C# 缺少求和数组时预期的缓存效果

C# 缺少求和数组时预期的缓存效果,c#,algorithm,performance,caching,cpu-cache,C#,Algorithm,Performance,Caching,Cpu Cache,我希望以下程序在性能方面完全受内存限制(阵列比L3缓存大得多) 因此,我期望长数组的和花费的时间几乎是int数组和的两倍 但这两种方法几乎同时进行: int sum took 81 ms, result = 4999999950000000 long sum took 87 ms, result = 4999999950000000 有人能解释一下吗 using System; using System.Diagnostics; using System.Linq; namespace M

我希望以下程序在性能方面完全受内存限制(阵列比L3缓存大得多)

因此,我期望长数组的和花费的时间几乎是int数组和的两倍

但这两种方法几乎同时进行:

 int sum took 81 ms, result = 4999999950000000
long sum took 87 ms, result = 4999999950000000
有人能解释一下吗

using System;
using System.Diagnostics;
using System.Linq;

namespace MemoryPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            const int count = 100_000_000;
            int[] intArray = Enumerable.Range(0, count).ToArray();
            long[] longArray = intArray.Select(x => (long)x).ToArray();
            Measure(() => intSum(intArray), " int sum");
            Measure(() => longSum(longArray), "long sum");
        }

        static long intSum(int[] array)
        {
            long sum = 0;
            for (int i = 0; i < array.Length; i++) sum += array[i];
            return sum;
        }

        static long longSum(long[] array)
        {
            long sum = 0;
            for (int i = 0; i < array.Length; i++) sum += array[i];
            return sum;
        }

        static void Measure(Func<long> calc, string description)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            long sum = calc();
            stopwatch.Stop();
            Console.WriteLine($"{description} took {stopwatch.ElapsedMilliseconds} ms, result = {sum}");
        }
    }
}
使用系统;
使用系统诊断;
使用System.Linq;
名称空间内存性能
{
班级计划
{
静态void Main(字符串[]参数)
{
常数整数计数=100_000_000;
int[]intArray=Enumerable.Range(0,count.ToArray();
long[]longArray=intArray.Select(x=>(long)x.ToArray();
度量(()=>intSum(intArray),“intSum”);
度量(()=>longSum(longArray),“长和”);
}
静态长整和(int[]数组)
{
长和=0;
对于(inti=0;i
如果我多次运行此操作,得到的结果大致相同,但更糟的是:(打印添加的结果以防万一)

那么长距离跑得更快?
int
版本没有这样做的一个原因是符号扩展。可能是这样。事实上我不知道还能是什么

但当数组的所有元素都被添加时,就会发生这种情况。如果我只取第8个元素(第8个,因为缓存线是64字节,长度是8,所以8适合缓存线),则会发生以下情况:

 int sum took 25 ms (624999950000000)
long sum took 49 ms (624999950000000)
 int sum took 23 ms (624999950000000)
long sum took 49 ms (624999950000000)
 int sum took 23 ms (624999950000000)
long sum took 48 ms (624999950000000)
 int sum took 23 ms (624999950000000)
long sum took 48 ms (624999950000000)
这是非常不同的,实际上
int
版本的速度大约是
long
版本的两倍,与两个版本预期的缓存未命中数相对应

因此,我可以由此得出结论,在“完整”版本中,显然有足够的算法(或至少“不是内存访问的东西”,包括循环开销)恰好隐藏了大部分缓存未命中惩罚,并且在
长版本中实际做的工作更少

另外,我认为我们应该记住,因为这是一种完全线性的访问模式,所以应该预期硬件预取会做得很好。自动预取的吞吐量可能是足够的,也可能不是足够的,但在这种情况下,它不应该是那么糟糕——进行一点计算就可以让预取“迎头赶上”,这并不是不合理的


仅使用第8个元素的相关代码:

    static long intSum(int[] array)
    {
        long sum = 0;
        for (int i = 0; i < array.Length; i += 8) sum += array[i];
        return sum;
    }

    static long longSum(long[] array)
    {
        long sum = 0;
        for (int i = 0; i < array.Length; i += 8) sum += array[i];
        return sum;
    }
静态长整数和(int[]数组)
{
长和=0;
对于(inti=0;i

计时打开

您测量的时间主要是“CPU时间”。 如果您只对数字进行求和,并像中那样省略整个内存访问,您将看到,在循环中简单地将所有数字相加所需的时间几乎相同,而不从数组/内存中读取它们:

static long noSum(long[] array)
{
        long sum = 0;
        for (int i = 0; i < array.Length; i ++) sum += i;
        return sum;
}
静态长noSum(长[]数组)
{
长和=0;
对于(inti=0;i

这意味着,即使CPU必须从内存中提取数据,并且无法将其全部保存在缓存中,它也可以非常有效地执行此操作,因为您不使用随机数组访问:对于循环,它有足够的时间在仍在执行计算时预取下一个缓存线。这导致几乎没有等待时间(推测执行任何人?!;-))。因此,这对你来说并不重要。显然,在那些需要更快地访问大量内存的情况下,就像在Harolds“稀疏”测试用例中一样

由于您从未使用求和,因此编译器可能会完全优化您的循环。不过,只是猜测而已。尝试输出结果以确保情况并非如此。我修改了代码——结果相同。循环展开和预取可能吗?因此,最终结果几乎完全是“CPU受限”的,而内存效应被预取隐藏了?这意味着80毫秒是计算总和所需的时间。你有没有检查过没有数组需要多长时间?你有没有意识到每次访问数组都要把
int
s转换成
long
s?@Sebastian:我想这是一个正确的提示:只求和(没有数组)大约需要60毫秒(75%的运行时间)。我得出了相同的结论:我相信时间就是“CPU”由于预取和循环展开,时间和内存访问被完全忽略。请参阅-它几乎完全“受CPU限制”,如果根本不访问内存,则需要花费几乎相同的时间。对于每第8个元素,访问“太随机”,我们会得到8倍多的缓存未命中/等待无序获取返回。
static long noSum(long[] array)
{
        long sum = 0;
        for (int i = 0; i < array.Length; i ++) sum += i;
        return sum;
}