[英]Looping for vs foreach difference in iterating dictionary c#
我有一个下面的foreach
循环来完成这项工作。 我很想知道我的以下情况 - 使用for
循环而不是foreach
循环来解决性能问题会更好吗?
因为我读到for
循环比foreach
循环快,所以我也有点困惑。
foreach (KeyValuePair<string, StringValues> v in values)
{
string key = v.Key;
StringValues val = v.Value;
if (val.Count > 0)
{
if (!string.IsNullOrWhiteSpace(val[0]))
{
switch (key)
{
case ABC:
One = val[0];
break;
case PQR:
Two = val[0];
break;
//.. bunch of other case block here with similar stuff
}
}
}
}
由于字典没有定义的顺序, IDictionary<>
接口中缺少任何类型的索引器,这使得在不使用foreach
/ GetEnumerator()
的情况下难以迭代。 鉴于...
Dictionary<int, int> dictionary = Enumerable.Range(0, 10).ToDictionary(i => i, i => -i);
...由于您知道键包含连续的整数范围,因此您可以使用for
循环遍历所有可能的键值...
// This exploits the fact that we know keys from 0..9 exist in dictionary
for (int key = 0; key < dictionary.Count; key++)
{
int value = dictionary[key];
// ...
}
然而,如果你不能做出这样的假设,它就会变得更加棘手。 您可以迭代Keys
集合属性以获取每个元素的键...但是该集合也不允许索引,因此您又回到了从foreach
与for
困境开始的地方。 但是,如果您坚持使用for
,那么一种方法是将Keys
复制到数组中,然后对其进行迭代...
// Copy the Keys property to an array to allow indexing
int[] keys = new int[dictionary.Count];
dictionary.Keys.CopyTo(keys, 0);
// This makes no assumptions about the distribution of keys in dictionary
for (int index = 0; index < dictionary.Count; index++)
{
int key = keys[index];
int value = Source[key];
// ...
}
当然, CopyTo()
将在您自己有机会这样做之前完整地枚举Keys
一次,这样只会损害性能。
如果您正在使用预先知道的一组固定键,或者您不介意每次字典键更改时都必须维护一个单独的键 collections,那么更好的方法是将键缓存在一个结构中可以索引...
int[] keyCache = Enumerable.Range(0, 10).ToArray();
// ...
// This retrieves known keys stored separately from dictionary
for (int index = 0; index < keyCache.Length; index++)
{
int key = keyCache[index];
int value = dictionary[key];
// ...
}
改用LINQ ElementAt()
方法可能很诱人; 毕竟,它很容易使用......
for (int index = 0; index < dictionary.Count; index++)
{
KeyValuePair<int, int> pair = dictionary.ElementAt(index);
// ...
}
但是,这对性能非常不利。 ElementAt()
只能在输入集合实现IList<>
时进行索引的特殊情况,而Dictionary<>
也没有IDictionary<>
从它继承。 否则,对于您尝试检索的每个索引,它都必须从头开始。 考虑枚举上面定义的整个 10 元素字典...
| Index requested | Elements enumerated | Total elements enumerated | |:---------------:|:----------------------------:|:-------------------------:| | 0 | 0 | 1 | | 1 | 0, 1 | 3 | | 2 | 0, 1, 2 | 6 | | 3 | 0, 1, 2, 3 | 10 | | 4 | 0, 1, 2, 3, 4 | 15 | | 5 | 0, 1, 2, 3, 4, 5 | 21 | | 6 | 0, 1, 2, 3, 4, 5, 6 | 28 | | 7 | 0, 1, 2, 3, 4, 5, 6, 7 | 36 | | 8 | 0, 1, 2, 3, 4, 5, 6, 7, 8 | 45 | | 9 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 | 55 |
将所有这些加起来,将需要55 次枚举来遍历一个 10 元素的字典,因此,为了通过消除foreach
/ GetEnumerator()
来提高性能,这只GetEnumerator()
调用隐藏起来,并使性能变得更糟.
至于这些方法的实际性能差异,这是我得到的结果......
// * Summary * BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.657 (1909/November2018Update/19H2) Intel Core i7 CPU 860 2.80GHz (Nehalem), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=3.1.201 [Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT .NET 4.8 : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT .NET Core 3.1 : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT | Method | Job | Runtime | Size | Mean | Error | StdDev | Ratio | RatioSD | |---------------------------------- |-------------- |-------------- |------- |--------------------:|------------------:|------------------:|----------:|--------:| | GetEnumerator | .NET 4.8 | .NET 4.8 | 10 | 118.4 ns | 1.71 ns | 1.76 ns | 1.02 | 0.02 | | ForEach | .NET 4.8 | .NET 4.8 | 10 | 116.4 ns | 1.44 ns | 1.28 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 10 | 147.6 ns | 2.96 ns | 3.17 ns | 1.26 | 0.02 | | While_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 10 | 149.2 ns | 1.72 ns | 1.61 ns | 1.28 | 0.02 | | For_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 10 | 154.5 ns | 1.16 ns | 0.97 ns | 1.33 | 0.01 | | While_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 10 | 160.8 ns | 1.93 ns | 1.71 ns | 1.38 | 0.01 | | For_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 10 | 177.5 ns | 1.37 ns | 1.14 ns | 1.53 | 0.02 | | While_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 10 | 185.6 ns | 3.69 ns | 4.80 ns | 1.59 | 0.05 | | For_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 10 | 154.5 ns | 2.83 ns | 2.64 ns | 1.33 | 0.03 | | While_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 10 | 155.3 ns | 2.35 ns | 2.08 ns | 1.33 | 0.02 | | For_ElementAt | .NET 4.8 | .NET 4.8 | 10 | 1,009.2 ns | 8.61 ns | 7.19 ns | 8.67 | 0.12 | | While_ElementAt | .NET 4.8 | .NET 4.8 | 10 | 1,140.9 ns | 14.36 ns | 13.43 ns | 9.81 | 0.16 | | | | | | | | | | | | GetEnumerator | .NET Core 3.1 | .NET Core 3.1 | 10 | 118.6 ns | 2.39 ns | 3.19 ns | 0.98 | 0.03 | | ForEach | .NET Core 3.1 | .NET Core 3.1 | 10 | 120.3 ns | 1.28 ns | 1.14 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 126.1 ns | 0.67 ns | 0.56 ns | 1.05 | 0.01 | | While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 135.5 ns | 2.28 ns | 2.02 ns | 1.13 | 0.02 | | For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 131.0 ns | 2.41 ns | 2.25 ns | 1.09 | 0.02 | | While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 133.9 ns | 1.42 ns | 1.19 ns | 1.11 | 0.01 | | For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 162.4 ns | 2.32 ns | 2.06 ns | 1.35 | 0.02 | | While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 166.3 ns | 1.29 ns | 1.21 ns | 1.38 | 0.02 | | For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 136.0 ns | 1.27 ns | 1.19 ns | 1.13 | 0.02 | | While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 10 | 142.3 ns | 2.84 ns | 4.59 ns | 1.14 | 0.02 | | For_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 10 | 952.4 ns | 10.08 ns | 8.94 ns | 7.92 | 0.13 | | While_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 10 | 953.8 ns | 8.86 ns | 7.40 ns | 7.93 | 0.12 | | | | | | | | | | | | GetEnumerator | .NET 4.8 | .NET 4.8 | 1000 | 9,344.9 ns | 80.50 ns | 75.30 ns | 1.00 | 0.01 | | ForEach | .NET 4.8 | .NET 4.8 | 1000 | 9,360.2 ns | 82.04 ns | 64.05 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 1000 | 15,122.4 ns | 81.71 ns | 68.23 ns | 1.62 | 0.01 | | While_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 1000 | 15,106.4 ns | 85.68 ns | 75.96 ns | 1.61 | 0.02 | | For_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 1000 | 16,160.3 ns | 270.09 ns | 252.64 ns | 1.73 | 0.03 | | While_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 1000 | 16,452.4 ns | 146.51 ns | 129.88 ns | 1.76 | 0.02 | | For_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 1000 | 17,407.1 ns | 251.38 ns | 222.84 ns | 1.86 | 0.03 | | While_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 1000 | 17,034.0 ns | 295.71 ns | 404.77 ns | 1.85 | 0.05 | | For_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 1000 | 16,277.5 ns | 69.91 ns | 58.38 ns | 1.74 | 0.02 | | While_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 1000 | 15,131.9 ns | 55.97 ns | 46.74 ns | 1.62 | 0.01 | | For_ElementAt | .NET 4.8 | .NET 4.8 | 1000 | 4,859,257.3 ns | 18,862.72 ns | 15,751.22 ns | 519.24 | 4.36 | | While_ElementAt | .NET 4.8 | .NET 4.8 | 1000 | 3,837,001.5 ns | 7,396.43 ns | 6,556.74 ns | 409.85 | 3.11 | | | | | | | | | | | | GetEnumerator | .NET Core 3.1 | .NET Core 3.1 | 1000 | 9,029.9 ns | 21.69 ns | 18.12 ns | 1.00 | 0.00 | | ForEach | .NET Core 3.1 | .NET Core 3.1 | 1000 | 9,022.4 ns | 13.08 ns | 10.92 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 11,396.9 ns | 18.42 ns | 15.38 ns | 1.26 | 0.00 | | While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 12,504.6 ns | 13.82 ns | 10.79 ns | 1.39 | 0.00 | | For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 12,244.1 ns | 73.90 ns | 69.13 ns | 1.36 | 0.01 | | While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 12,437.4 ns | 22.48 ns | 18.77 ns | 1.38 | 0.00 | | For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 13,717.9 ns | 36.98 ns | 30.88 ns | 1.52 | 0.00 | | While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 14,099.6 ns | 20.44 ns | 18.12 ns | 1.56 | 0.00 | | For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 12,640.4 ns | 23.31 ns | 19.47 ns | 1.40 | 0.00 | | While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 1000 | 12,610.5 ns | 20.97 ns | 17.51 ns | 1.40 | 0.00 | | For_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 1000 | 3,402,799.3 ns | 15,880.59 ns | 14,077.73 ns | 377.13 | 1.73 | | While_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 1000 | 3,399,305.2 ns | 5,822.01 ns | 5,161.06 ns | 376.76 | 0.74 | | | | | | | | | | | | GetEnumerator | .NET 4.8 | .NET 4.8 | 100000 | 885,621.4 ns | 1,617.29 ns | 1,350.51 ns | 1.00 | 0.00 | | ForEach | .NET 4.8 | .NET 4.8 | 100000 | 884,591.8 ns | 1,781.29 ns | 1,390.72 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,424,062.0 ns | 2,791.28 ns | 2,474.39 ns | 1.61 | 0.00 | | While_Indexer_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,435,667.1 ns | 3,696.89 ns | 3,277.19 ns | 1.62 | 0.00 | | For_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,502,486.1 ns | 3,750.98 ns | 3,325.15 ns | 1.70 | 0.00 | | While_TryGetValue_ConsecutiveKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,558,335.7 ns | 4,619.63 ns | 3,857.60 ns | 1.76 | 0.00 | | For_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,685,000.7 ns | 4,676.88 ns | 3,651.40 ns | 1.90 | 0.01 | | While_Indexer_CopyToKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,722,418.0 ns | 3,431.67 ns | 3,042.08 ns | 1.95 | 0.01 | | For_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,499,782.0 ns | 2,951.84 ns | 2,616.73 ns | 1.70 | 0.00 | | While_Indexer_CachedKeys | .NET 4.8 | .NET 4.8 | 100000 | 1,583,570.2 ns | 3,880.57 ns | 3,440.03 ns | 1.79 | 0.00 | | For_ElementAt | .NET 4.8 | .NET 4.8 | 100000 | 37,917,621,633.3 ns | 47,744,618.60 ns | 44,660,345.86 ns | 42,868.63 | 93.80 | | While_ElementAt | .NET 4.8 | .NET 4.8 | 100000 | 38,343,003,642.9 ns | 262,502,616.47 ns | 232,701,732.10 ns | 43,315.66 | 229.53 | | | | | | | | | | | | GetEnumerator | .NET Core 3.1 | .NET Core 3.1 | 100000 | 900,980.9 ns | 2,477.29 ns | 2,068.65 ns | 1.00 | 0.00 | | ForEach | .NET Core 3.1 | .NET Core 3.1 | 100000 | 899,775.7 ns | 1,040.30 ns | 868.70 ns | 1.00 | 0.00 | | For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,177,153.8 ns | 1,689.80 ns | 1,411.06 ns | 1.31 | 0.00 | | While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,255,795.4 ns | 2,562.23 ns | 2,139.58 ns | 1.40 | 0.00 | | For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,226,163.3 ns | 2,317.36 ns | 1,809.25 ns | 1.36 | 0.00 | | While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,245,130.0 ns | 4,146.38 ns | 3,237.22 ns | 1.38 | 0.00 | | For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,430,340.4 ns | 7,834.82 ns | 6,945.37 ns | 1.59 | 0.01 | | While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,472,807.7 ns | 5,363.80 ns | 4,754.87 ns | 1.64 | 0.01 | | For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,289,902.4 ns | 2,739.78 ns | 2,139.04 ns | 1.43 | 0.00 | | While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 | 100000 | 1,276,484.8 ns | 4,652.23 ns | 3,884.82 ns | 1.42 | 0.00 | | For_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 100000 | 33,717,209,257.1 ns | 200,565,125.50 ns | 177,795,759.65 ns | 37,460.45 | 216.07 | | While_ElementAt | .NET Core 3.1 | .NET Core 3.1 | 100000 | 34,064,932,086.7 ns | 225,399,893.36 ns | 210,839,200.10 ns | 37,841.10 | 204.02 |
...从我使用BenchmarkDotNet编写的这个小程序中...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace SO61507883
{
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
public class Benchmarks
{
public static IReadOnlyList<int> DictionarySizes
{
get;
} = Array.AsReadOnly(new int[] { 10, 1_000 });
[ParamsSource(nameof(DictionarySizes))]
public int Size
{
get; set;
}
public Dictionary<int, int> Source
{
get; set;
}
// Only used by the *_CachedKeys() benchmark methods
public int[] KeyCache
{
get; set;
}
[GlobalSetup()]
public void Setup()
{
Source = Enumerable.Range(0, Size)
.ToDictionary(i => i, i => -i);
KeyCache = new int[Size];
Source.Keys.CopyTo(KeyCache, 0);
}
[Benchmark()]
public (int keySum, int valueSum) GetEnumerator()
{
int keySum = 0;
int valueSum = 0;
using (Dictionary<int, int>.Enumerator enumerator = Source.GetEnumerator())
while (enumerator.MoveNext())
{
KeyValuePair<int, int> pair = enumerator.Current;
keySum += pair.Key;
valueSum += pair.Value;
}
return (keySum, valueSum);
}
[Benchmark(Baseline = true)]
public (int keySum, int valueSum) ForEach()
{
int keySum = 0;
int valueSum = 0;
foreach (KeyValuePair<int, int> pair in Source)
{
keySum += pair.Key;
valueSum += pair.Value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) For_Indexer_ConsecutiveKeys()
{
int keySum = 0;
int valueSum = 0;
// This exploits the fact that we know keys from 0..Size-1 exist in Source
for (int key = 0; key < Size; key++)
{
int value = Source[key];
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) While_Indexer_ConsecutiveKeys()
{
int key = 0;
int keySum = 0;
int valueSum = 0;
// This exploits the fact that we know keys from 0..Size-1 exist in Source
while (key < Size)
{
int value = Source[key];
keySum += key++;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) For_TryGetValue_ConsecutiveKeys()
{
int keySum = 0;
int valueSum = 0;
// This exploits the fact that we know keys from 0..Size-1 exist in Source
for (int key = 0; key < Size; key++)
if (Source.TryGetValue(key, out int value))
{
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) While_TryGetValue_ConsecutiveKeys()
{
int key = 0;
int keySum = 0;
int valueSum = 0;
// This exploits the fact that we know keys from 0..Size-1 exist in Source
while (key < Size)
if (Source.TryGetValue(key, out int value))
{
keySum += key++;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) For_Indexer_CopyToKeys()
{
// Copy the Keys property to an array to allow indexing
int[] keys = new int[Size];
Source.Keys.CopyTo(keys, 0);
int keySum = 0;
int valueSum = 0;
// This makes no assumptions about the distribution of keys in Source
for (int index = 0; index < Size; index++)
{
int key = keys[index];
int value = Source[key];
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) While_Indexer_CopyToKeys()
{
// Copy the Keys property to an array to allow indexing
int[] keys = new int[Size];
Source.Keys.CopyTo(keys, 0);
int index = 0;
int keySum = 0;
int valueSum = 0;
// This makes no assumptions about the distribution of keys in Source
while (index < Size)
{
int key = keys[index++];
int value = Source[key];
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) For_Indexer_CachedKeys()
{
int keySum = 0;
int valueSum = 0;
// This retrieves known keys stored separately from Source
for (int index = 0; index < Size; index++)
{
int key = KeyCache[index];
int value = Source[key];
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) While_Indexer_CachedKeys()
{
int index = 0;
int keySum = 0;
int valueSum = 0;
// This retrieves known keys stored separately from Source
while (index < Size)
{
int key = KeyCache[index++];
int value = Source[key];
keySum += key;
valueSum += value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) For_ElementAt()
{
int keySum = 0;
int valueSum = 0;
for (int index = 0; index < Size; index++)
{
KeyValuePair<int, int> pair = Source.ElementAt(index);
keySum += pair.Key;
valueSum += pair.Value;
}
return (keySum, valueSum);
}
[Benchmark()]
public (int keySum, int valueSum) While_ElementAt()
{
int index = 0;
int keySum = 0;
int valueSum = 0;
while (index < Size)
{
KeyValuePair<int, int> pair = Source.ElementAt(index++);
keySum += pair.Key;
valueSum += pair.Value;
}
return (keySum, valueSum);
}
}
static class Program
{
static void Main(string[] args)
{
switch (args?.FirstOrDefault()?.ToUpper())
{
case "BENCHMARK":
BenchmarkMethods();
break;
case "TEST":
TestMethods();
break;
default:
DisplayUsage();
break;
}
}
static void DisplayUsage()
{
string assemblyLocation = Assembly.GetEntryAssembly().Location;
string assemblyFileName = System.IO.Path.GetFileName(assemblyLocation);
Console.WriteLine($"{assemblyFileName} {{ BENCHMARK | TEST }}");
Console.WriteLine("\tBENCHMARK - Benchmark dictionary enumeration methods.");
Console.WriteLine("\t TEST - Display results of dictionary enumeration methods.");
}
static void BenchmarkMethods()
{
BenchmarkDotNet.Running.BenchmarkRunner.Run<Benchmarks>();
}
static void TestMethods()
{
// Find, setup, and call the benchmark methods the same way BenchmarkDotNet would
Benchmarks benchmarks = new Benchmarks();
IEnumerable<MethodInfo> benchmarkMethods = benchmarks.GetType()
.GetMethods()
.Where(
method => method.CustomAttributes.Any(
attributeData => typeof(BenchmarkAttribute).IsAssignableFrom(attributeData.AttributeType)
)
);
foreach (MethodInfo method in benchmarkMethods)
{
Console.WriteLine($"{method.Name}():");
foreach (int size in Benchmarks.DictionarySizes)
{
benchmarks.Size = size;
benchmarks.Setup();
(int, int) result = ((int, int)) method.Invoke(benchmarks, Array.Empty<object>());
Console.WriteLine($"\t{size:N0} elements => {result}");
}
}
}
}
}
请注意,上面的代码从Benchmarks.DictionarySizes
属性中省略了100_000
,因为它增加了一个多小时的运行时间。
结论:
foreach
/ GetEnumerator()
是迭代字典的最快方法。for
或while
循环充其量会稍微慢一些,但它仍然较慢。for
循环中使用ElementAt()
的性能很差,只有字典越大越慢。只有在非常极端的情况下才有意义。 foreach
的性能下降是您必须写入另一个变量, for
您不需要。 foreach 基本上是这样的:
for(int i = 0, i < something.Length; i++)
{
var item = something[i]; //which is why you can just use the item from collection
//your code using the item var...
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.