改进的基准:
- 硬件:Intel Core i7-10700K x64,.NET 5,优化构建。运行 .NET 5 的 LINQPad 6 和运行 .NET Fx 4.8 的 LINQPad 5。
- 时间以毫秒为单位,保留 3 个小数位。
0.001ms
是 1 微秒。
- 我不确定
Stopwatch
它的实际分辨率,因为它取决于系统,所以不要强调微秒级的差异。
- 基准测试被重新运行了数十次,结果一致。显示的时间是所有运行的平均值。
- 结论:通过在构造函数中进行设置,整体加速始终保持 10-20%
capacity
Dictionary<String,String>
。
。网: |
.NET 框架 4.8 |
.NET 5 |
初始容量为 1,000,000 |
|
|
构造函数 |
1.170ms |
0.003ms |
填写循环 |
353.420 毫秒 |
181.846 毫秒 |
总时间 |
354.590 毫秒 |
181.880 毫秒 |
无初始容量 |
|
|
构造函数 |
0.001ms |
0.001ms |
填写循环 |
400.158ms |
228.687 毫秒 |
总时间 |
400.159 毫秒 |
228.688ms |
从设置初始容量加速 |
|
|
时间 |
45.569 毫秒 |
46.8ms |
加速 % |
11% |
20% |
10
我确实对较小的初始大小( 、100
、1000
、10000
和)重复了基准测试,并且100000
在这些大小上也观察到了 10-20% 的加速,但绝对而言,在需要几分之一毫秒的操作上加速了 20%
- 虽然我看到了一致的结果(显示的数字是平均值),但有一些警告:
- 这个基准测试是在 1,000,000 个项目的相当大的规模上执行的,但有紧密的循环(即循环体内没有太多其他事情发生),这不是一个现实的场景。因此,请始终对您自己的代码进行剖析和基准测试以确保知道,而不是相信您在 Internet 上找到的随机基准(就像这个)。
- 基准测试不会隔离生成数百万个左右
String
实例所花费的时间(由i.ToString()
.
- 引用类型
String
(值类型(例如 a ValueTuple
)。还有其他类型大小的因素需要考虑。
- 随着从 .NET Framework 4.8 到 .NET 5 的大幅改进,这意味着如果您在 .NET 6 或更高版本上运行,则不应相信这些数字。
- 此外,不要假设较新的 .NET 版本会_总是)使事情变得更快:有时 .NET 更新和操作系统安全补丁的性能实际上会恶化。
// Warmup:
{
var foo1 = new Dictionary<string, string>();
var foo2 = new Dictionary<string, string>( capacity: 10_000 );
foo1.Add( "foo", "bar" );
foo2.Add( "foo", "bar" );
}
Stopwatch sw = Stopwatch.StartNew();
// Pre-set capacity:
TimeSpan pp_initTime;
TimeSpan pp_populateTime;
{
var dict1 = new Dictionary<string, string>(1000000);
pp_initTime = sw.GetElapsedAndRestart();
for (int i = 0; i < 1000000; i++)
{
dict1.Add(i.ToString(), i.ToString());
}
}
pp_populateTime = sw.GetElapsedAndRestart();
//
TimeSpan empty_initTime;
TimeSpan empty_populateTime;
{
var dict2 = new Dictionary<string, string>();
empty_initTime = sw.GetElapsedAndRestart();
for (int i = 0; i < 1000000; i++)
{
dict2.Add(i.ToString(), i.ToString());
}
}
empty_populateTime = sw.GetElapsedAndRestart();
//
Console.WriteLine("Pre-set capacity. Init time: {0:N3}ms, Fill time: {1:N3}ms, Total time: {2:N3}ms.", pp_initTime.TotalMilliseconds, pp_populateTime.TotalMilliseconds, ( pp_initTime + pp_populateTime ).TotalMilliseconds );
Console.WriteLine("Empty capacity. Init time: {0:N3}ms, Fill time: {1:N3}ms, Total time: {2:N3}ms.", empty_initTime.TotalMilliseconds, empty_populateTime.TotalMilliseconds, ( empty_initTime + empty_populateTime ).TotalMilliseconds );
// Extension methods:
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
public static TimeSpan GetElapsedAndRestart( this Stopwatch stopwatch )
{
TimeSpan elapsed = stopwatch.Elapsed;
stopwatch.Restart();
return elapsed;
}
原始基准:
原始基准,没有冷启动预热阶段和较低精度的DateTime
时序:
- 与容量 (
dict1
) 的总时间是1220.778ms
(建设和人口)。
- 没有能力(
dict2
)总时间是1502.490ms
(建设和人口)。
- 因此,与不设置容量相比,容量节省了 320 毫秒(~20%)。
static void Main(string[] args)
{
const int ONE_MILLION = 1000000;
DateTime start1 = DateTime.Now;
{
var dict1 = new Dictionary<string, string>( capacity: ONE_MILLION );
for (int i = 0; i < ONE_MILLION; i++)
{
dict1.Add(i.ToString(), i.ToString());
}
}
DateTime stop1 = DateTime.Now;
DateTime start2 = DateTime.Now;
{
var dict2 = new Dictionary<string, string>();
for (int i = 0; i < ONE_MILLION; i++)
{
dict2.Add(i.ToString(), i.ToString());
}
}
DateTime stop2 = DateTime.Now;
Console.WriteLine("Time with size initialized: " + (stop1.Subtract(start1)) + "\nTime without size initialized: " + (stop2.Subtract(start2)));
Console.ReadLine();
}