c# - .NET字典插入的怪异性能行为

标签 c# .net performance dictionary

我有两个具有不同值类型的字典:Dictionary<int, string[]>Dictionary<int, int[]>。假设我们在一个循环中生成随机数组,并将其插入到字典中(在C#中)。

var d1 = new Dictionary<int, string[]>();
var d2 = new Dictionary<int, int[]>();
var sw = Stopwatch.StartNew();
for (int i = 0; i < 40000000; i++)
{
    string[] sarr = new string[10];
    for (int j = 0; j < 10; j++)
    {
        sarr[j] = j.ToString();
    }
    int[] iarr = new int[10];
    for (int j = 0; j < 10; j++)
    {
        iarr[j] = j;
    }
    d1[i] = sarr; // (1)
    d2[i] = iarr; // (2)
}
sw.Stop();
请注意for循环的最后两行。当我运行上面的代码时,在我的计算机上花费大约13.9秒。现在,当我仅注释掉(1)时,大约需要13.7秒。如果我只注释(2),则大约需要20秒。换句话说,删除(2)会变得慢很多!我重复了多次,可以确认其行为是一致的。
谁能解释一下这怎么可能?
我进行此实验是因为我注意到,即使我在两个字典中使用相同的键,插入string[]的速度也比插入int[]的速度慢。我想知道为什么插入string []比插入int[]还要慢。
所以我的问题是双重的:(1)为什么从上面的代码中删除一行会使事情变慢,(2)为什么插入string[]比插入int[]慢?

仅供引用,我正在使用最新的.NET 5(5.0.103)。我在Windows和Linux上都尝试了该代码,其行为是相同的。使用调试或 Release模式时,我始终看到相同的问题。

当我比较注释版本和原始版本的IL时,注释版本没有按预期方式调用字典的set_Item函数。其他事物或多或少是相同的。
IL_0079: ldloc.1      // dictionary2
IL_007a: ldloc.3      // key
IL_007b: ldloc.s      numArray
IL_007d: callvirt     instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32[]>::set_Item(!0/*int32*/, !1/*int32[]*/)
IL_0082: nop
例如,当我注释掉(2)时,删除了上面的部分。

为了帮助解决此问题,我使用Benchmark.NET创建了一个简单的存储库:https://github.com/sangkilc/TestDictionary。在这个仓库中,我减少了迭代次数(从40M减少到4M),因为它花费的时间太长。
在我的机器上,结果是:
.NET Core SDK=5.0.103
  [Host]     : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
  DefaultJob : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT


|   Method |    Mean |    Error |   StdDev |
|--------- |--------:|---------:|---------:|
| TestBoth | 1.269 s | 0.0222 s | 0.0208 s |
|  TestOne | 1.381 s | 0.0257 s | 0.0241 s |
根据@TheodorZoulias的观察,如果我将d2修改为2D数组,则区别变得更加明显:
|   Method |    Mean |    Error |   StdDev |
|--------- |--------:|---------:|---------:|
| TestBoth | 1.137 s | 0.0195 s | 0.0163 s |
|  TestOne | 1.373 s | 0.0246 s | 0.0345 s |

最佳答案

这不是答案,只是想炫耀一些图片
我已经在Release中为您的两种情况编译了代码:

  • 同时具有(1)和(2)
  • 仅带有(1)

  • 请注意,我删除了与Stopwatch相关的代码,因为我们只对Dictionary感兴趣。
    我的机器上装有dotTrace,所以得到了一些分析结果(逐行)。
    对于这两种情况:
    按线程树:
    enter image description here
    通过方法:
    enter image description here
    对于仅(1)种情况:
    按线程树:
    enter image description here
    通过方法:
    enter image description here
    由于您提到的模式是一致的,因此我只运行了一次配置文件。
    从结果中,我们可以看出与Dictionary相关的功能不是造成所谓“怪异性能行为”的主要因素,GC可能是主要原因。

    关于c# - .NET字典插入的怪异性能行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66477084/

    相关文章:

    原始内存流上的 C# 数学性能

    c++ - 一维数组是否比 Eigen 动态 vector 快?

    PHP/MySQL Prepared Statements - 一个用户可以从另一个用户的准备好的查询中获益吗?

    c# - 索引器作为 C# 中接口(interface)的一部分

    c# - 将列表拆分为重复列表和非重复列表

    c# - 是否应该在某个地方处理所有抛出的异常?

    c# - System.ComponentModel.DataAnnotations.Schema 命名空间冲突

    .net - WCF RIA 服务和 Windows Azure => 实体没有定义 key

    linux - 特定于源的多播操作系统/驱动程序性能

    c# - 使用FileHelperAsyncEngine崩溃无一异常(exception)