繁体   English   中英

在迭代字典 c# 中循环 for vs foreach 差异

[英]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集合属性以获取每个元素的键...但是该集合也不允许索引,因此您又回到了从foreachfor困境开始的地方。 但是,如果您坚持使用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()是迭代字典的最快方法。
  • 根据运行时,当您可以对您的键做出一些假设时,使用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...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM