簡體   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