【问题标题】:Speed of reading from contiguous vs non-contiguous arrays从连续数组和非连续数组读取的速度
【发布时间】:2011-08-19 14:32:10
【问题描述】:

我重写了一个计算库来改进内存管理,并发现这会提高速度。在原始版本中,它使用一个数组,其成员在内存中相隔 12 个双精度数(即 96 个字节),而我的数组是连续的。

这种差异会带来多大的速度提升?

【问题讨论】:

  • 数组不是分配在连续的内存位置吗?什么是非连续数组?顺便说一句,您使用的是哪种语言?
  • 我为一些提供数组内存的 fortran 例程编写了一个包装器。至于“非连续数组” - 比如说在 C 中你会有 char array[50][50]。这本质上是 50 个 50 字节的数组。关键是访问 50 个数组中的每个数组的第一个元素比访问第一个数组的所有元素慢吗? (这类似于 fortran 中发生的事情)。请注意,我不是在编写纯 fortran 的访问例程。
  • 如果是大小为 (m,n) 的二维数组,则 (i,j) 的内存偏移量如下,memOffset(i,j) = sizeOf(arrElement) * ((m-i)*n + j)。而在一维数组中它是memOffset(i) = sizeOf(arrElement) * i。所以性能下降是不存在的。
  • 如果我们增加 i,但是,当访问元素时,我们必须乘以 n。你是说这不会带来任何开销?与阅读相比可能并不明显?
  • 嗯,您正在比较 sizeOfElement * isizeOf(arrElement) * ((m-i)*n + j)k * ik * ((m-i)*n + j) 的性能。我想,除非您在缓慢或过载的系统上运行它,否则我几乎不会有所作为。您是在 PC 还是其他设备上运行代码?从 PC 的角度来看,性能差异可以忽略不计。

标签: performance memory


【解决方案1】:

我创建了一个小型测试程序,用于计算一维和二维数组的数组元素访问时间。它是 C# .NET 中的 Console 应用程序,内置于 Release 模式(已启用优化)。如果一维数组的大小是m,那么二维数组的大小就是m x m

public class PhysicsCalculator
{
    public const Int32 ArrayDimension = 1000;

    public long CalculateSingleDimensionPerformance()
    {
        var arr = new double[ArrayDimension];

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        for (Int32 i = 0; i < ArrayDimension; i++)
        {
            arr[i] = i;
        }

        stopwatch.Stop();

        return stopwatch.ElapsedTicks;
    }

    public long CalculateDoubleDimensionPerformance()
    {
        var arr = new double[ArrayDimension, ArrayDimension];

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        for (Int32 i = 0; i < ArrayDimension; i++)
        {
            arr[i, 5] = i;
        }

        stopwatch.Stop();

        return stopwatch.ElapsedTicks;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var physicsCalculator = new PhysicsCalculator();

        // This is a dummy call to tell the runtime to jit the methods before hand (to avoid jitting on first call)
        physicsCalculator.CalculateSingleDimensionPerformance();
        physicsCalculator.CalculateDoubleDimensionPerformance();

        Console.WriteLine("Number of ticks per seconds = " + new TimeSpan(0, 0, 1).Ticks);
        Console.WriteLine();

        const int numberOfRepetetions = 1000;
        long elapsedTicks = 0;

        for (var i = 0; i < numberOfRepetetions; i++)
        {
            elapsedTicks += physicsCalculator.CalculateSingleDimensionPerformance();
        }

        Console.WriteLine("1D array : ");
        GenerateReport(elapsedTicks, numberOfRepetetions);

        elapsedTicks = 0;
        for (var i = 0; i < numberOfRepetetions; i++)
        {
            elapsedTicks += physicsCalculator.CalculateDoubleDimensionPerformance();
        }

        Console.WriteLine("2D array : ");
        GenerateReport(elapsedTicks, numberOfRepetetions);

        // Wait before exit
        Console.Read();
    }

    private static void GenerateReport(long elapsedTicks, int numberOfRepetetions)
    {
        var oneSecond = new TimeSpan(0, 0, 1);

        Console.WriteLine("Array size = " + PhysicsCalculator.ArrayDimension);
        Console.WriteLine("Ticks (avg) = " + elapsedTicks / numberOfRepetetions);
        Console.WriteLine("Ticks (for {0} repetitions) = {1}", numberOfRepetetions, elapsedTicks);
        Console.WriteLine("Time taken (avg) = {0} ms", (elapsedTicks * oneSecond.TotalMilliseconds) / (numberOfRepetetions * oneSecond.Ticks));
        Console.WriteLine("Time taken (for {0} repetitions) = {1} ms", numberOfRepetetions,
           (elapsedTicks * oneSecond.TotalMilliseconds) / oneSecond.Ticks);

        Console.WriteLine();
    }
}

在我的机器(2.8 GHz Phenom II 四核,8 GB DDR2 800 MHz RAM,Windows 7 Ultimate x64)上的结果是

Number of ticks per seconds = 10000000

1D array : Array size = 1000 
Ticks (avg) = 52 
Ticks (for 1000 repetitions) = 52598 
Time taken (avg) = 0.0052598 ms 
Time taken (for 1000 repetitions) = 5.2598 ms

2D array : Array size = 1000 
Ticks (avg) = 13829
Ticks (for 1000 repetitions) = 13829984 
Time taken (avg) = 1.3829984 ms 
Time taken (for 1000 repetitions) = 1382.9984 ms

有趣的是,结果非常清楚,2D 数组元素的访问时间明显大于 1D 数组元素的访问时间。


确定所用时间是否是数组大小的函数

  • 对于100的数组大小
Number of ticks per seconds = 10000000

1D array : 
Array size = 100
Ticks (avg) = 20
Ticks (for 1000 repetitions) = 20552
Time taken (avg) = 0.0020552 ms
Time taken (for 1000 repetitions) = 2.0552 ms

2D array : 
Array size = 100
Ticks (avg) = 326
Ticks (for 1000 repetitions) = 326039
Time taken (avg) = 0.0326039 ms
Time taken (for 1000 repetitions) = 32.6039 ms
  • 对于20 的数组大小
Number of ticks per seconds = 10000000

1D array : 
Array size = 20
Ticks (avg) = 16
Ticks (for 1000 repetitions) = 16653
Time taken (avg) = 0.0016653 ms
Time taken (for 1000 repetitions) = 1.6653 ms

2D array : 
Array size = 20
Ticks (avg) = 21
Ticks (for 1000 repetitions) = 21147
Time taken (avg) = 0.0021147 ms
Time taken (for 1000 repetitions) = 2.1147 ms
  • 对于12 的数组大小(您的用例)
Number of ticks per seconds = 10000000

1D array : 
Array size = 12
Ticks (avg) = 16
Ticks (for 1000 repetitions) = 16548
Time taken (avg) = 0.0016548 ms
Time taken (for 1000 repetitions) = 1.6548 ms

2D array : 
Array size = 12
Ticks (avg) = 20
Ticks (for 1000 repetitions) = 20762
Time taken (avg) = 0.0020762 ms
Time taken (for 1000 repetitions) = 2.0762 ms

如您所见,数组大小确实会影响元素访问时间。但是,在数组大小为12 的用例中,差异大约是(0.0016548 ms 用于 1D 与 0.0020762 ms 用于 2D)25% 即 1D 访问比 2D 访问快 25%。


在二维数组的情况下,当较低的数组尺寸较小时

在上述示例中,如果一维数组的大小为m,则二维数组的大小为m x m。当二维数组的大小减小到m x 2 时,我得到m = 12 的以下结果

1D array : 
Array size = 12
Ticks (avg) = 16
Ticks (for 1000 repetitions) = 16100
Time taken (avg) = 0.00161 ms
Time taken (for 1000 repetitions) = 1.61 ms

2D array : 
Array size = 12 x 2
Ticks (avg) = 16
Ticks (for 1000 repetitions) = 16324
Time taken (avg) = 0.0016324 ms
Time taken (for 1000 repetitions) = 1.6324 ms

在这种情况下,差异几乎没有 1.3%。

为了衡量您系统的性能,我建议您在 FORTRAN 中转换上述代码并使用实际值运行基准测试。

【讨论】:

  • 嗯,这是一个巨大的差异,我不得不承认 - 这比我预期的要大。这可以归结为编译器优化吗?此外,可以将多少差异归结为数组大小? IE。 2Dticks 与 1Dticks 的比率如何作为“ArrayDimension”的函数表现。 (在我的情况下,数组维度为 12)抱歉,我要求进行更多测试,我不是 C# 开发人员,我是 linux 用户。非常感谢!
  • 代码在 .NET 中运行,因此与原生 C/C++ 相比,它是一个 managed 环境。结果适用于optimized 构建。我将不得不检查访问时间是否是数组大小的函数。我需要更多关于您正在比较性能的系统的信息,例如 CPU、RAM、操作系统、编译器以及您用作数组索引器的数据类型(Int32/Int16/等)。
  • 如前所述,代码在 fortran 中,因此索引只是一个标准的 fortran 整数,它是一个双精度的 fortran 数组。至于实际的系统,我不能老实告诉你。我通过 ssh-ing 到一组 linux 服务器上运行代码。他们都在运行 SLC5(这是科学的 linux)。编译器目前只是 GCC 4.1.2 或 4.3.3。我希望这会有所帮助。
  • 我添加了确定数组访问时间是否决定数组元素访问时间的结果。似乎数组越大访问时间越慢。
  • 我怀疑在 2D 测试函数内部,大部分时间都花在了初始化(归零)数组上,因此与 1D 测试函数相比,它的工作量是 1024 倍。也许应该使用采样分析器来查看时间花在了哪里。
猜你喜欢
  • 2015-01-15
  • 2017-02-09
  • 1970-01-01
  • 2021-02-21
  • 2017-09-14
  • 2016-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多