.NET9 实现对象深拷贝和浅拷贝的性能测试
在 .NET
中,深拷贝(Deep Copy) 和 浅拷贝(Shallow Copy) 是对象复制的两种方式,它们的区别在于是否复制对象内部引用的其他对象。
浅拷贝(Shallow Copy)
定义:
浅拷贝创建一个新对象,并将原对象的所有字段复制到新对象中。如果是值类型字段,直接复制其值;如果是引用类型字段,则只复制引用地址(指针),而不复制引用的对象本身。
特点:
- 原始对象与副本共享引用类型的成员。
- 修改其中一个对象的引用成员会影响另一个对象。
- 通常速度快、开销小。
实现方式:
- 使用
MemberwiseClone()
方法实现浅拷贝。
示例代码:
public object ShallowCopy()
{return this.MemberwiseClone();
}
图示说明:
OriginalPerson.Address ───┐├─ 指向同一个 Address 对象
CopyPerson.Address ─────┘
深拷贝(Deep Copy)
定义:
深拷贝不仅复制对象本身,还会递归地复制对象所引用的其他对象,生成一个完全独立的副本。
特点:
- 原始对象和副本之间没有共享的引用对象。
- 修改副本不会影响原始对象。
- 通常比浅拷贝更耗性能。
实现方式:
- 手动实现:为每个引用类型字段创建新实例。
- 序列化反序列化(如
System.Text.Json
,Newtonsoft.Json
等)。 - 表达式树或反射生成器(如:第三方包
DeepCloner,FastDeepCloner
等)。
示例代码
- 使用 JSON 序列化:
public Person DeepCopy()
{var json = JsonSerializer.Serialize(this);return JsonSerializer.Deserialize<Person>(json)!;
}
图示说明:
OriginalPerson.Address ───┐├─ 指向不同的 Address 对象(内容相同)
CopyPerson.Address ─────┘
总结对比
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
引用对象复制 | 不复制,仅复制引用 | 复制,生成新对象 |
内存占用 | 小 | 大 |
性能 | 快 | 较慢 |
修改互不影响 | 否(修改引用对象会互相影响) | 是(完全独立) |
典型实现方法 | MemberwiseClone() | 序列化、手动复制、第三方库 |
对象设计
设计一个 Person
类,里面包含了一个 Address
引用类型字段:
internal class Person
{public string Name { get; set; } = string.Empty;public int Age { get; set; }public Address Address { get; set; } = new();
}
ShallowCopy()
:复制了Name
、Age
的值,但Address
字段只是复制了引用。DeepCopy()
:完整复制整个对象图,包括Address
实例。.NET 9
推荐使用System.Text.Json
或其他现代方式实现深拷贝。
📦 项目准备
.net
控制台项目信息
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><PublishAot>true</PublishAot><InvariantGlobalization>true</InvariantGlobalization></PropertyGroup><ItemGroup><PackageReference Include="Datadog.Trace.BenchmarkDotNet" Version="2.61.0" /><PackageReference Include="System.Text.Json" Version="9.0.6" /></ItemGroup></Project>
- 创建一个
Person
对象,分别实现对象的深拷贝和浅拷贝方
using System.Text.Json;namespace BenchmarkTest.examples.Copy;// 创建一个包含深拷贝和浅拷贝方法的类
public class Person
{public string Name { get; set; } = string.Empty;public int Age { get; set; }public Address Address { get; set; } = new();// 浅拷贝public object ShallowCopy() => this.MemberwiseClone();// 深拷贝 (使用 System.Linq.Expressions 手动实现)public Person DeepCopy1() => new(){Name = this.Name,Age = this.Age,Address = new Address{Street = this.Address.Street,City = this.Address.City}};// 深拷贝 (使用 System.Text.Json)public Person DeepCopy2(){var json = JsonSerializer.Serialize(this);return JsonSerializer.Deserialize<Person>(json)!;}
}[Serializable]
public class Address
{public string Street { get; set; } = string.Empty;public string City { get; set; } = string.Empty;
}
- 拷贝测试类
CopyBenchmark
//===========================
// 拷贝测试类 CopyBenchmark
//===========================using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Datadog.Trace.BenchmarkDotNet;namespace BenchmarkTest.examples.Copy;[DatadogDiagnoser]
[MemoryDiagnoser]
public class CopyBenchmark
{private Person _person = new();[GlobalSetup]public void Setup(){_person = new Person{Name = "John",Age = 30,Address = new Address { Street = "Main St", City = "New York" }};}[Benchmark]public object ShallowCopyTest() => _person.ShallowCopy();[Benchmark]public object DeepCopy1Test() => _person.DeepCopy1();[Benchmark]public object DeepCopy2Test() => _person.DeepCopy2();public static void Run(IConfig config){var summary = BenchmarkRunner.Run<CopyBenchmark>(config);Console.WriteLine(summary);}
}
🏁 启动基准测试
- 在
Program.cs
中运行基准测试
using BenchmarkDotNet.Configs;
using BenchmarkTest.examples.Copy;
using Datadog.Trace.BenchmarkDotNet;Console.WriteLine("Hello, BenchmarkDotNetTest!");var config = DefaultConfig.Instance.WithDatadog();
CopyBenchmark.Run(config);
- 运行测试
dotnet run -c Release
输出信息:
以下是对 BenchmarkDotNet
测试报告的详细分析与解释:
📊 测试环境概览
项目 | 说明 |
---|---|
BenchmarkDotNet 版本 | v0.13.2 |
操作系统 | Windows 11 (10.0.26100) |
.NET SDK | 9.0.301 |
运行时 | .NET 9.0.6(AOT/AVX2) |
🧪 性能指标说明
指标 | 含义 |
---|---|
Mean | 平均执行时间(纳秒 ns) |
Error | 置信区间误差范围(通常为 ± 值) |
StdDev | 标准差,衡量运行时间波动性 |
Median | 中位数,比平均值更稳定 |
Gen0 | Gen0 GC 次数(每 1000 次操作) |
Allocated | 每次操作分配的内存大小 |
🔍 测试方法分析
✅ ShallowCopyTest
- Mean: 69.06 ns
- Allocated: 40 B
- 特点:
- 使用
MemberwiseClone()
实现浅拷贝。 - 只复制对象本身字段,引用类型字段共享地址。
- 性能高、内存占用低。
- 不涉及复杂序列化或深拷贝逻辑。
- 使用
结论:速度快,适用于不需要独立副本的场景。
✅ DeepCopy1Test
- Mean: 27.87 ns
- Allocated: 104 B
- 实现方式:
- 手动实现的深拷贝(new 新对象并赋值)。
- 或者使用了高性能的表达式树/源生成器等技术。
结论:虽然比浅拷贝慢一点,但仍是高效且完全独立的对象副本,适合大多数业务场景。
⚠️ DeepCopy2Test
- Mean: 1,693.59 ns(约 60 倍于 DeepCopy1)
- Allocated: 1,168 B(约 10 倍于 DeepCopy1)
- 实现方式:
- 使用
JSON
序列化反序列化(如System.Text.Json
)。 - 或者第三方库。
- 使用
结论:性能明显下降,适合数据结构复杂但需要通用性的场景。若对性能敏感,应考虑优化深拷贝方式(如手动克隆或使用 FastDeepCloner 等库)。
📌 总结对比
方法 | 执行时间 | 内存分配 | 是否独立副本 | 推荐用途 |
---|---|---|---|---|
ShallowCopyTest | ✅ 最快 | ✅ 最少 | ❌ 否 | 快速只读复制 |
DeepCopy1Test | ⚠️ 较快 | ⚠️ 适中 | ✅ 是 | 高性能深拷贝 |
DeepCopy2Test | ❌ 慢 | ❌ 较多 | ✅ 是 | 复杂结构 / 快速开发 |
🛠️ 建议优化方向
- 避免使用 JSON 序列化进行频繁深拷贝,除非对象结构非常复杂。
- 优先手动实现深拷贝逻辑 或使用源生成工具(如
FastDeepCloner
)提升性能。 - 若需通用性,可结合缓存机制减少重复深拷贝开销。
- 对性能要求极高时,可使用
Span<T>
或Memory<T>
进行无堆分配拷贝。