【问题标题】:Why are 1-dimensional arrays faster than Jagged arrays in C#?为什么一维数组比 C# 中的交错数组更快?
【发布时间】:2013-08-12 11:48:41
【问题描述】:

我很好奇一维数组是否比锯齿状数组快,我测量了以下代码块的性能:

测试 1:锯齿状数组

double[][][][] jagged = ArrayExtensions.Get4DMatrix<double>(100, 100, 50, 50, 0);
for (int iter = 0; iter < 5; iter++)
{
    sw.Restart();
    for (i = 0; i < 100; i++)
    {
        for (j = 0; j < 100; j++)
        {
            for (k = 0; k < 50; k++)
            {
                for (l = 0; l < 50; l++)
                {
                    test = jagged[i][j][k][l];
                    jagged[i][j][k][l] = test;
                }
            }
        }
    }
    Console.WriteLine("Jagged Arrays, Test {0}: {1} ms", iter, sw.ElapsedMilliseconds);
}

测试 2:一维数组

double[] single = ArrayExtensions.Get1DArray<double>(25000000);
for (int iter = 0; iter < 5; iter++)
{
    sw.Restart();
    for (i = 0; i < 100; i++)
    {
        for (j = 0; j < 100; j++)
        {
            for (k = 0; k < 50; k++)
            {
                for (l = 0; l < 50; l++)
                {
                    test = single[i * 100 + j * 100 + k * 50 + l];
                    single[i * 100 + j * 100 + k * 50 + l] = test;
                }
            }
        }
    }
    Console.WriteLine("Single Arrays, Test {0}: {1} ms", iter, sw.ElapsedMilliseconds);
}

运行测试产生:

Jagged Arrays, Test 0: 1447 m
Jagged Arrays, Test 1: 1429 m
Jagged Arrays, Test 2: 1431 m
Jagged Arrays, Test 3: 1430 m
Jagged Arrays, Test 4: 1429 m

Single Arrays, Test 0: 386 ms
Single Arrays, Test 1: 387 ms
Single Arrays, Test 2: 386 ms
Single Arrays, Test 3: 387 ms
Single Arrays, Test 4: 387 ms

另外,我只对数组赋值,然后只从数组读取,运行测试,结果具有相同的比率。

我原以为一维数组比交错数组快,但当我看到最后一个块的执行时间仅为第一个块的 27% 时,我感到非常惊讶。

有人可以解释为什么会出现这种巨大差异吗?使用一维数组是否有任何缺点(除了代码可读性,它显然变得更难了,而且可能会增加出错的风险)?

代码是在未优化的构建中执行的。在优化构建中,两个测试在每次迭代中都在 100 毫秒内执行,但我认为这与循环内执行的代码有关。不过,一维数组比锯齿状数组快 50%。

【问题讨论】:

  • 为什么执行 4“取消引用向量并通过索引解析,并进行空值和边界检查”(ldelem - 和 stelem 用于分配)比执行 1“取消引用慢”一个向量并通过索引解析,带有空值和边界检查”?
  • @VimalCK while true,在这种情况下它是一个锯齿状数组 - 所以 4 个单独的向量,而不是一个多维数组
  • 请记住,在您的锯齿状数组版本中,有 多个 空值和边界检查。

标签: c# arrays performance optimization


【解决方案1】:
   test = single[i * 100 + j * 100 + k * 50 + l];

一位聪明的程序员曾经说过:“永远不要相信你没有伪造过自己的基准”。可能是无意的,这是您代码中的一个非常讨厌的错误,它让您比较苹果和橘子。乘数完全错误。 i 索引必须乘以 100*50*50,j 索引必须乘以 50*50。

副作用是您更多更有可能有效地使用 CPU 缓存,因为您处理的内存要少得多。有很大的不同,RAM 很慢。

【讨论】:

  • 绝对!我猜可以写成single[((i * 100 + j) * 50 + k) * 50 + l]
【解决方案2】:

影响性能的一个主要因素是数据缓存未命中的数量。内存被分成称为缓存线的块,根据机器的不同,缓存线可能在 16-256 字节左右。访问高速缓存行中的任何数据字节的成本与访问其中的所有内容一样多。最近访问的高速缓存行保存在 CPU 内核中的一个小高速缓存中,并且可以非常快速地再次访问。最近没有访问到足以进入一级缓存的行将在二级缓存中查找,二级缓存更大但访问速度不快。在那里找不到的行可能会在第三级缓存中查找(理论上,第四、第五、第六等,尽管我认为任何机器都不会走那么远)。一条指令需要在任何缓存中找不到的数据,其执行时间可能比使用 1 级缓存可以满足的要长数十倍。

您的程序可能不是衡量线性与交错数组相对性能的最佳指标,因为您使用的是完全顺序访问。这意味着大多数访问将由最快的(1 级)缓存处理。正如 pspet 所指出的,取消引用四个嵌套对象比计算单个偏移量并使用它需要更多的工作。如果一切都来自 1 级缓存,那么实际数据访问成本低这一事实意味着这种额外的努力将占主导地位。

我建议您尝试改变循环的顺序并监控性能。在“发布”模式下构建并在没有附加调试器的情况下运行以获得准确的时序结果。我猜想交换你的两个内部循环会减慢两个版本的代码大致相同(大多数数据请求可能不会被一级缓存满足,但对内部引用的请求会),带来他们的相对时间更接近。交换所有循环会稍微损害线性数组版本的性能,但可能会导致嵌套锯齿状数组的性能很糟糕(您的外部数组可能会停留在一级缓存中,但嵌套引用可能不会,结果是许多元素访问会导致两到三个完整的缓存未命中)。

在 .NET 中,对于占用超过 85,000 字节的数组,尤其是在它们的生命周期很短的情况下,会降低性能,因此在许多情况下,两级锯齿状数组可能是最佳选择。例如,如果数据项是 64 字节,那么在 64 位系统上的两级嵌套将允许一个拥有 10,000 个数组,每个数组包含 1,024 个项,而任何项都不会超过 85K。如果您需要超过 10,000,000 个项目,访问模式将决定您是使用更大的数组还是使用第三级嵌套更好,但是在各种数组大小中,上述方法是最好的。

【讨论】:

    【解决方案3】:

    也许是因为“锯齿状数组”是指针数组(指向数组)... 在您的示例中,您有 4 个间接级别:

    jagged[i][j][k][l]
    
    • 从“锯齿状”获取偏移量 i
    • 从上一个结果中获取偏移量 j
    • 从上一个结果中获取偏移量 k
    • 从上一个结果中获取偏移量 l

    【讨论】:

      猜你喜欢
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      • 1970-01-01
      • 2010-10-02
      • 2011-06-06
      • 2014-03-26
      相关资源
      最近更新 更多