改善されたベンチマーク:
- ハードウェア: 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 |
.NET5 |
初期容量 1,000,000 |
|
|
コンストラクタ |
1.170ms |
0.003ms |
ループを埋める |
353.420ms |
181.846ミリ秒 |
合計時間 |
354.590ms |
181.880ms |
初期容量なし |
|
|
コンストラクタ |
0.001ms |
0.001ms |
ループを埋める |
400.158ms |
228.687ms |
合計時間 |
400.159ms |
228.688ミリ秒 |
初期容量設定からの高速化 |
|
|
時間 |
45.569ms |
46.8ms |
スピードアップ % |
11% |
20% |
- より小さい初期サイズ (
10
、100
、1000
、10000
および100000
) のベンチマークを繰り返しましたが、それらのサイズでも 10 ~ 20% の高速化が観察されましたが、絶対的には、数分の 1 ミリ秒かかる操作で 20% の高速化が見られました。
- 一定の結果が得られましたが (表示されている数値は平均値です)、注意点がいくつかあります。
- このベンチマークは、1,000,000 アイテムというかなり極端なサイズで実行されましたが、現実的なシナリオではないタイト ループ (つまり、ループ本体内で他に多くのことが行われていない) を使用して実行されました。したがって、インターネットで見つけたランダムなベンチマークを信頼するのではなく、常に自分のコードをプロファイリングしてベンチマークし、確実に知るようにしてください(このようなものです)。
String
ベンチマークは、100 万個ほどのインスタンスの生成に費やされた時間を分離しません( i.ToString()
.
- 参照型 (
String
) がキーと値の両方に使用され、ネイティブ ポインター サイズ (x64 では 8 バイト) と同じサイズを使用するため、キーや値がより大きな値を使用する場合、再実行すると結果が異なります。値の型 ( a などValueTuple
)。考慮すべき他のタイプサイズ要因もあります。
- .NET Framework 4.8 から .NET 5 に大幅に改善されたため、.NET 6 以降で実行している場合は、これらの数値を信頼してはいけません。
- また、新しい .NET リリースが_常に) 高速化すると仮定しないでください..NET の更新と OS セキュリティ パッチの両方で実際にパフォーマンスが低下した場合があります。
// 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();
}