C#微基准测试:为什么重置聚合值会使循环更快?

C#微基准测试:为什么重置聚合值会使循环更快?,c#,microbenchmark,C#,Microbenchmark,考虑以下两个不同的函数ComputeA和ComnputeB: using System; using System.Diagnostics; namespace BenchmarkLoop { class Program { private static double[] _dataRow; private static double[] _dataCol; public static double ComputeA(doubl

考虑以下两个不同的函数
ComputeA
ComnputeB

using System;
using System.Diagnostics;

namespace BenchmarkLoop
{
    class Program
    {
        private static double[] _dataRow;
        private static double[] _dataCol;

        public static double ComputeA(double[] col, double[] row)
        {
            var rIdx = 0;
            var value = 0.0;

            for (var i = 0; i < col.Length; ++i)
            {
                for (var cIdx = 0; cIdx < col.Length; ++cIdx, ++rIdx)
                    value += col[cIdx] * row[rIdx];
            }

            return value;
        }

        public static double ComputeB(double[] col, double[] row)
        {
            var rIdx = 0;
            var value = 0.0;

            for (var i = 0; i < col.Length; ++i)
            {
                value = 0.0;
                for (var cIdx = 0; cIdx < col.Length; ++cIdx, ++rIdx)
                    value += col[cIdx] * row[rIdx];
            }

            return value;
        }

        public static double ComputeC(double[] col, double[] row)
        {
            var rIdx = 0;
            var value = 0.0;

            for (var i = 0; i < col.Length; ++i)
            {
                var tmp = 0.0;
                for (var cIdx = 0; cIdx < col.Length; ++cIdx, ++rIdx)
                    tmp += col[cIdx] * row[rIdx];
                value += tmp;
            }

            return value;
        }

        static void Main(string[] args)
        {
            _dataRow = new double[2500];
            _dataCol = new double[50];

            var random = new Random();
            for (var i = 0; i < _dataRow.Length; i++)            
                _dataRow[i] = random.NextDouble();
            for (var i = 0; i < _dataCol.Length; i++)
                _dataCol[i] = random.NextDouble();

            var nRuns = 1000000;

            var stopwatch = new Stopwatch();
            stopwatch.Start();
            for (var i = 0; i < nRuns; i++)
                ComputeA(_dataCol, _dataRow);
            stopwatch.Stop();
            var t0 = stopwatch.ElapsedMilliseconds;

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < nRuns; i++)
                ComputeC(_dataCol, _dataRow);
            stopwatch.Stop();
            var t1 = stopwatch.ElapsedMilliseconds;

            Console.WriteLine($"Time ComputeA: {t0} - Time ComputeC: {t1}");
            Console.ReadKey();
        }
    }
}
使用系统;
使用系统诊断;
命名空间基准循环
{
班级计划
{
专用静态双[]\u数据行;
专用静态双[]_数据列;
公共静态双计算机(双[]列,双[]行)
{
var-rIdx=0;
var值=0.0;
对于(变量i=0;i
它们的不同之处在于每次调用内部循环之前变量值的“重置”。我已经运行了几种不同类型的基准测试,都启用了“优化代码”,32位和64位,以及不同大小的数据数组。通常,
ComputeB
的速度大约快25%。我也可以用BenchmarkDotNet重现这些结果。但我无法解释。有什么想法吗?我还使用“英特尔VTune放大器2019”检查了生成的汇编代码:对于这两个函数,JIT结果完全相同,加上要重置
值的额外行:
因此,在汇编程序级别上,没有任何魔法可以使代码更快。这种影响还有其他可能的解释吗?如何验证呢

下面是BenchmarkDotNet的结果(参数
N
的大小为
\u dataCol
\u dataRow
的大小始终为N^2):

比较
ComputeA
ComputeC
的结果:

用于
ComputeA
(左)和
ComputeC
(右)的JIT装配:


差异非常小:在块2中,变量
tmp
被设置为
0
(存储在寄存器
xmml
),在块6中,
tmp
被添加到返回结果
值中。所以,总的来说,这并不奇怪。只是运行时很神奇;)

我希望外部循环在
ComputeB
中得到完全优化。25%是巨大的。你在测量别的东西。这两种方法不一样。第一个计算所有迭代的总和。第二个只返回最后一个的和iteration@PanagiotisKanavos“但是,为什么第二次计算要快得多?”GROO:JIT不是那么聪明(但是即使是C++编译器也损坏不了这个!),正如你可以从汇编代码(见截图)@安东尼勒塞克(Onon Nejjek)所说的,他提到他可以用BenchmarkDotNet来验证结果。虽然这段代码没有使用它。