循环 vs 迭代字典 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与 S 的起点。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完整地列举一次,所以这只会损害性能。

如果您正在使用一组预先知道的固定键,或者您不介意每次字典的键更改时都必须维护单独的键集合,那么稍微好一点的方法是将键缓存在可以索引的结构中......

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];
// ...
}

改用 LINQElementAt()方法可能很诱人;毕竟,它很容易使用...

for (int index = 0; index < dictionary.Count; index++)
{
KeyValuePair<int, int> pair = dictionary.ElementAt(index);
// ...
}

但是,这对性能非常不利ElementAt()只有在输入集合实现IList<>时才能作为索引的特殊情况,Dictionary<>没有,IDictionary<>也没有从它继承。 否则,对于您尝试检索的每个索引,它必须从头开始。 考虑枚举上面定义的整个 10 元素字典......

|请求索引 |     枚举的元素 |枚举的元素总数 | |:---------------:|:----------------------------:|:-------------------------:| |       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()调用转移到幕后并使性能变差

至于这些方法的实际性能差异,这是我得到的结果......

//*总结* BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.657 (1909/十一月2018更新/19H2) 英特尔酷睿 i7 CPU 860 2.80GHz (Nehalem), 1 个 CPU, 8 个逻辑内核和 4 个物理内核 .NET Core SDK=3.1.201 [主机] : .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 |                           方法 |          人才招聘 |      运行时 |  尺寸 |               平均值 |            错误 |           标准开发 |    比率 |比率SD | |---------------------------------- |-------------- |-------------- |------- |--------------------:|------------------:|------------------:|----------:|--------:| |                    获取枚举器 |     .NET 4.8 |     .NET 4.8 |    10 |           118.4 纳秒 |          1.71 纳秒 |          1.76 纳秒 |     1.02 |   0.02 | |                          福尔奇 |     .NET 4.8 |     .NET 4.8 |    10 |           116.4 纳秒 |          1.44 纳秒 |          1.28 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |    10 |           147.6 纳秒 |          2.96 纳秒 |          3.17 纳秒 |     1.26 |   0.02 | |    While_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |    10 |           149.2 纳秒 |          1.72 纳秒 |          1.61 纳秒 |     1.28 |   0.02 ||  For_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |    10 |           154.5 纳秒 |          1.16 纳秒 |          0.97 纳秒 |     1.33 |   0.01 | |While_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |    10 |           160.8 纳秒 |          1.93 纳秒 |          1.71 纳秒 |     1.38 |   0.01 | |           For_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |    10 |           177.5 纳秒 |          1.37 纳秒 |          1.14 纳秒 |     1.53 |   0.02 | |         While_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |    10 |           185.6 纳秒 |          3.69 纳秒 |          4.80 纳秒 |     1.59 |   0.05 | |           For_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |    10 |           154.5 纳秒 |          2.83 纳秒 |          2.64 纳秒 |     1.33 |   0.03 | |         While_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |    10 |           155.3 纳秒 |          2.35 纳秒 |          2.08 纳秒 |     1.33 |   0.02 | |                    For_ElementAt |     .NET 4.8 |     .NET 4.8 |    10 |         1,009.2 纳秒 |          8.61 纳秒 |          7.19 纳秒 |     8.67 |   0.12 | |                  While_ElementAt |     .NET 4.8 |     .NET 4.8 |    10 |         1,140.9 纳秒 |         14.36 纳秒 |         13.43 纳秒 |     9.81 |   0.16 | |                                  |              |              |       |                    |                  |                  |          |        | |                    GetEnumerator | .NET Core 3.1 | .NET Core 3.1 |    10 |           118.6 纳秒 |          2.39 纳秒 |          3.19 纳秒 |     0.98 |   0.03 | |                          ForEach | .NET Core 3.1 | .NET Core 3.1 |    10 |           120.3 纳秒 |          1.28 纳秒 |          1.14 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           126.1 纳秒 |          0.67 纳秒 |          0.56 纳秒 |     1.05 |   0.01 | |    While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           135.5 纳秒 |          2.28 纳秒 |          2.02 纳秒 |     1.13 |   0.02 | |  For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           131.0 纳秒 |          2.41 纳秒 |          2.25 纳秒 |     1.09 |   0.02 | |While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           133.9 纳秒 |          1.42 纳秒 |          1.19 纳秒 |     1.11 |   0.01 | |           For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           162.4 纳秒 |          2.32 纳秒 |          2.06 纳秒 |     1.35 |   0.02 | |         While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           166.3 纳秒 |          1.29 纳秒 |          1.21 纳秒 |     1.38 |   0.02 | |           For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           136.0 纳秒 |          1.27 纳秒 |          1.19 纳秒 |     1.13 |   0.02 | |         While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |    10 |           142.3 纳秒 |          2.84 纳秒 |          4.59 纳秒 |     1.14 |   0.02 | |                    For_ElementAt | .NET Core 3.1 | .NET Core 3.1 |    10 |           952.4 纳秒 |         10.08 纳秒 |          8.94 纳秒 |     7.92 |   0.13 | |                  While_ElementAt | .NET Core 3.1 | .NET Core 3.1 |    10 |           953.8 纳秒 |          8.86 纳秒 |          7.40 纳秒 |     7.93 |   0.12 | |                                  |              |              |       |                    |                  |                  |          |        | |                    获取枚举器 |     .NET 4.8 |     .NET 4.8 |  1000 |         9,344.9 纳秒 |         80.50 纳秒 |         75.30 纳秒 |     1.00 |   0.01 | |                          福尔奇 |     .NET 4.8 |     .NET 4.8 |  1000 |         9,360.2 纳秒 |         82.04 纳秒 |         64.05 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        15,122.4 纳秒 |         81.71 纳秒 |         68.23 纳秒 |     1.62 |   0.01 | |    While_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        15,106.4 纳秒 |         85.68 纳秒 |         75.96 纳秒 |     1.61 |   0.02 | |  For_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        16,160.3 纳秒 |        270.09 纳秒 |        252.64 纳秒 |     1.73 |   0.03 | |While_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        16,452.4 纳秒 |        146.51 纳秒 |        129.88 纳秒 |     1.76 |   0.02 | |           For_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        17,407.1 纳秒 |        251.38 纳秒 |        222.84 纳秒 |     1.86 |   0.03 | |         While_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        17,034.0 纳秒 |        295.71 纳秒 |        404.77 纳秒 |     1.85 |   0.05 ||           For_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        16,277.5 纳秒 |         69.91 纳秒 |         58.38 纳秒 |     1.74 |   0.02 | |         While_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |  1000 |        15,131.9 纳秒 |         55.97 纳秒 |         46.74 纳秒 |     1.62 |   0.01 | |                    For_ElementAt |     .NET 4.8 |     .NET 4.8 |  1000 |     4,859,257.3 纳秒 |     18,862.72 纳秒 |     15,751.22 纳秒 |   519.24 |   4.36 | |                  While_ElementAt |     .NET 4.8 |     .NET 4.8 |  1000 |     3,837,001.5 纳秒 |      7,396.43 纳秒 |      6,556.74 纳秒 |   409.85 |   3.11 | |                                  |              |              |       |                    |                  |                  |          |        | |                    GetEnumerator | .NET Core 3.1 | .NET Core 3.1 |  1000 |         9,029.9 纳秒 |         21.69 纳秒 |         18.12 纳秒 |     1.00 |   0.00 | |                          ForEach | .NET Core 3.1 | .NET Core 3.1 |  1000 |         9,022.4 纳秒 |         13.08 纳秒 |         10.92 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        11,396.9 纳秒 |         18.42 纳秒 |         15.38 纳秒 |     1.26 |   0.00 | |    While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        12,504.6 纳秒 |         13.82 纳秒 |         10.79 纳秒 |     1.39 |   0.00 | |  For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        12,244.1 纳秒 |         73.90 纳秒 |         69.13 纳秒 |     1.36 |   0.01 | |While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        12,437.4 纳秒 |         22.48 纳秒 |         18.77 纳秒 |     1.38 |   0.00 | |           For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        13,717.9 纳秒 |         36.98 纳秒 |         30.88 纳秒 |     1.52 |   0.00 | |         While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        14,099.6 纳秒 |         20.44 纳秒 |         18.12 纳秒 |     1.56 |   0.00 | |           For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        12,640.4 纳秒 |         23.31 纳秒 |         19.47 纳秒 |     1.40 |   0.00 | |         While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |  1000 |        12,610.5 纳秒 |         20.97 纳秒 |         17.51 纳秒 |     1.40 |   0.00 | |                    For_ElementAt | .NET Core 3.1 | .NET Core 3.1 |  1000 |     3,402,799.3 纳秒 |     15,880.59 纳秒 |     14,077.73 纳秒 |   377.13 |   1.73 | |                  While_ElementAt | .NET Core 3.1 | .NET Core 3.1 |  1000 |     3,399,305.2 纳秒 |      5,822.01 纳秒 |      5,161.06 纳秒 |   376.76 |   0.74 | |                                  |              |              |       |                    |                  |                  |          |        | |                    获取枚举器 |     .NET 4.8 |     .NET 4.8 |100000 |       885,621.4 纳秒 |      1,617.29 纳秒 |      1,350.51 纳秒 |     1.00 |   0.00 | |                          福尔奇 |     .NET 4.8 |     .NET 4.8 |100000 |       884,591.8 纳秒 |      1,781.29 纳秒 |      1,390.72 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,424,062.0 纳秒 |      2,791.28 纳秒 |      2,474.39 纳秒 |     1.61 |   0.00 | |    While_Indexer_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,435,667.1 纳秒 |      3,696.89 纳秒 |      3,277.19 纳秒 |     1.62 |   0.00 | |  For_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,502,486.1 纳秒 |      3,750.98 纳秒 |      3,325.15 纳秒 |     1.70 |   0.00 | |While_TryGetValue_ConsecutiveKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,558,335.7 纳秒 |      4,619.63 纳秒 |      3,857.60 纳秒 |     1.76 |   0.00 | |           For_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,685,000.7 纳秒 |      4,676.88 纳秒 |      3,651.40 纳秒 |     1.90 |   0.01 | |         While_Indexer_CopyToKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,722,418.0 纳秒 |      3,431.67 纳秒 |      3,042.08 纳秒 |     1.95 |   0.01 | |           For_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,499,782.0 纳秒 |      2,951.84 纳秒 |      2,616.73 纳秒 |     1.70 |   0.00 | |         While_Indexer_CachedKeys |     .NET 4.8 |     .NET 4.8 |100000 |     1,583,570.2 纳秒 |      3,880.57 纳秒 |      3,440.03 纳秒 |     1.79 |   0.00 | |                    For_ElementAt |     .NET 4.8 |     .NET 4.8 |100000 |37,917,621,633.3 纳秒 | 47,744,618.60 纳秒 | 44,660,345.86 纳秒 |42,868.63 |  93.80 | |                  While_ElementAt |     .NET 4.8 |     .NET 4.8 |100000 |38,343,003,642.9 纳秒 |262,502,616.47 纳秒 |232,701,732.10 纳秒 |43,315.66 | 229.53 ||                                  |              |              |       |                    |                  |                  |          |        | |                    GetEnumerator | .NET Core 3.1 | .NET Core 3.1 |100000 |       900,980.9 纳秒 |      2,477.29 纳秒 |      2,068.65 纳秒 |     1.00 |   0.00 | |                          ForEach | .NET Core 3.1 | .NET Core 3.1 |100000 |       899,775.7 纳秒 |      1,040.30 纳秒 |        868.70 纳秒 |     1.00 |   0.00 | |      For_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,177,153.8 纳秒 |      1,689.80 纳秒 |      1,411.06 纳秒 |     1.31 |   0.00 | |    While_Indexer_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,255,795.4 纳秒 |      2,562.23 纳秒 |      2,139.58 纳秒 |     1.40 |   0.00 | |  For_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,226,163.3 纳秒 |      2,317.36 纳秒 |      1,809.25 纳秒 |     1.36 |   0.00 | |While_TryGetValue_ConsecutiveKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,245,130.0 纳秒 |      4,146.38 纳秒 |      3,237.22 纳秒 |     1.38 |   0.00 | |           For_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,430,340.4 纳秒 |      7,834.82 纳秒 |      6,945.37 纳秒 |     1.59 |   0.01 | |         While_Indexer_CopyToKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,472,807.7 纳秒 |      5,363.80 纳秒 |      4,754.87 纳秒 |     1.64 |   0.01 | |           For_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,289,902.4 纳秒 |      2,739.78 纳秒 |      2,139.04 纳秒 |     1.43 |   0.00 | |         While_Indexer_CachedKeys | .NET Core 3.1 | .NET Core 3.1 |100000 |     1,276,484.8 纳秒 |      4,652.23 纳秒 |      3,884.82 纳秒 |     1.42 |   0.00 | |                    For_ElementAt | .NET Core 3.1 | .NET Core 3.1 |100000 |33,717,209,257.1 纳秒 |200,565,125.50 纳秒 |177,795,759.65 纳秒 |37,460.45 | 216.07 | |                  While_ElementAt | .NET Core 3.1 | .NET Core 3.1 |100000 |34,064,932,086.7 纳秒 |225,399,893.36 纳秒 |210,839,200.10 纳秒 |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()是迭代字典的最快方法。
  • 根据运行时的不同,当您可以对密钥做出一些假设时,使用forwhile循环充其量会稍微慢一些,但它仍然较慢
  • 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...
}

最新更新