基于杨中科老师的GC示例,写一个Benchmark
很久之前,我看到杨中科老师的一篇文章
谁说.NET没有GC调优?只改一行代码就让程序不再占用内存受益匪浅。
最近遇到了类似的问题,不过细节有点忘了,于是又回顾了一下,顺便写了一个Benchmark,加强了一下印象。
// ==================== 数据类型定义 ====================
// 1. 原始 Class 定义
public class WeatherForecastClass
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
// 2. 优化方案二:Struct 定义
public struct WeatherForecastStruct
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
// ==================== 基准测试类 ====================
[MemoryDiagnoser] // 开启内存诊断,关注内存分配和GC次数
public class ToArrayContext
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
// 模拟文中提到的 150,000 条数据
private const int Count = 150_000;
/// <summary>
/// 方案一(基线):Class + ToArray()
/// 问题:产生大对象(LOH),内存占用高,GC压力大
/// </summary>
[Benchmark(Baseline = true)]
public WeatherForecastClass[] GetClassToArray()
{
return Enumerable.Range(1, Count).Select(index => new WeatherForecastClass
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
/// <summary>
/// 方案一 的 List 对照
/// </summary>
[Benchmark]
public List<WeatherForecastClass> GetClassToList()
{
return Enumerable.Range(1, Count).Select(index => new WeatherForecastClass
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToList();
}
/// <summary>
/// 方案一优化:Class + 流式返回 (IEnumerable)
/// 优势:没有 ToArray() 产生的数组对象,流水线作业,内存占用极低
/// 注意:为了在 Benchmark 中公平比较“执行完整逻辑”,这里强制迭代一次模拟消费过程。
/// </summary>
[Benchmark]
public void GetClassStream()
{
var result = Enumerable.Range(1, Count).Select(index => new WeatherForecastClass
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
});
// 模拟消费者(如 JSON 序列化器)逐条读取数据
// 只有迭代才会真正创建对象
foreach (var _ in result) { }
}
/// <summary>
/// 方案二优化:Struct + ToArray()
/// 优势:Struct 内存布局更紧凑,无对象头开销,数组占用内存减半
/// 注意:虽然内存占用减半,但仍然有一次 ToArray() 的大块内存分配
/// </summary>
[Benchmark]
public WeatherForecastStruct[] GetStructToArray()
{
return Enumerable.Range(1, Count).Select(index => new WeatherForecastStruct
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
}
// ==================== 程序入口 ====================
//public class Program
//{
// public static void Main(string[] args)
// {
// // 运行基准测试
// var summary = BenchmarkRunner.Run<ToArrayContext>();
// }
//}
运行结果如下:
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.6466/22H2/2022Update)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK=8.0.200
[Host] : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| GetClassToArray | 13.504 ms | 0.2631 ms | 0.3132 ms | 1.00 | 0.00 | 1000.0000 | 718.7500 | 296.8750 | 5.72 MB | 1.00 |
| GetClassToList | 13.169 ms | 0.1357 ms | 0.1269 ms | 0.97 | 0.03 | 1000.0000 | 718.7500 | 296.8750 | 5.72 MB | 1.00 |
| GetClassStream | 5.977 ms | 0.0235 ms | 0.0196 ms | 0.44 | 0.01 | 757.8125 | - | - | 4.58 MB | 0.80 |
| GetStructToArray | 5.488 ms | 0.0162 ms | 0.0151 ms | 0.41 | 0.01 | 328.1250 | 328.1250 | 328.1250 | 2.29 MB | 0.40 |

浙公网安备 33010602011771号