.NET 4.6 RC x64 的速度是 x86 的两倍(发行版)

Posted

技术标签:

【中文标题】.NET 4.6 RC x64 的速度是 x86 的两倍(发行版)【英文标题】:.NET 4.6 RC x64 is twice as slow as x86 (release version) 【发布时间】:2015-05-12 15:28:44 【问题描述】:

Net 4.6 RC x64 的速度是 x86(发布版)的两倍:

考虑这段代码:

class SpectralNorm

    public static void Main(String[] args)
    
        int n = 5500;
        if (args.Length > 0) n = Int32.Parse(args[0]);

        var spec = new SpectralNorm();
        var watch = Stopwatch.StartNew();
        var res = spec.Approximate(n);

        Console.WriteLine("0:f9 -- 1", res, watch.Elapsed.TotalMilliseconds);
    

    double Approximate(int n)
    
        // create unit vector
        double[] u = new double[n];
        for (int i = 0; i < n; i++) u[i] = 1;

        // 20 steps of the power method
        double[] v = new double[n];
        for (int i = 0; i < n; i++) v[i] = 0;

        for (int i = 0; i < 10; i++)
        
            MultiplyAtAv(n, u, v);
            MultiplyAtAv(n, v, u);
        

        // B=AtA         A multiplied by A transposed
        // v.Bv /(v.v)   eigenvalue of v 
        double vBv = 0, vv = 0;
        for (int i = 0; i < n; i++)
        
            vBv += u[i] * v[i];
            vv += v[i] * v[i];
        

        return Math.Sqrt(vBv / vv);
    


    /* return element i,j of infinite matrix A */
    double A(int i, int j)
    
        return 1.0 / ((i + j) * (i + j + 1) / 2 + i + 1);
    

    /* multiply vector v by matrix A */
    void MultiplyAv(int n, double[] v, double[] Av)
    
        for (int i = 0; i < n; i++)
        
            Av[i] = 0;
            for (int j = 0; j < n; j++) Av[i] += A(i, j) * v[j];
        
    

    /* multiply vector v by matrix A transposed */
    void MultiplyAtv(int n, double[] v, double[] Atv)
    
        for (int i = 0; i < n; i++)
        
            Atv[i] = 0;
            for (int j = 0; j < n; j++) Atv[i] += A(j, i) * v[j];
        
    

    /* multiply vector v by matrix A and then by matrix A transposed */
    void MultiplyAtAv(int n, double[] v, double[] AtAv)
    
        double[] u = new double[n];
        MultiplyAv(n, v, u);
        MultiplyAtv(n, u, AtAv);
    

在我的机器上,x86 发布版本需要 4.5 秒才能完成,而 x64 需要 9.5 秒。 x64 是否需要任何特定的标志/设置?

更新

事实证明,RyuJIT 在这个问题上起了作用。 如果在 app.config 中启用了useLegacyJit,结果会有所不同,这一次 x64 更快。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
  </startup>
  <runtime>
    <useLegacyJit enabled="1" />
 </runtime>
</configuration>

更新

现已将该问题报告给 CLR 团队coreclr, issue 993

【问题讨论】:

我不熟悉光谱规范,这是需要考虑的大量代码。你能给我们总结一下这是在做什么——成百上千的大型浮点双精度矩阵的矩阵运算,其中也有平方根和除法?你能在这两个方面都分析一下吗,你能看看生成的汇编程序是否有任何明显的悲观情绪吗? 您是否正在运行发布版本,而不是在调试器中运行它? 值得在 for 循环中运行几次并忽略前几次迭代,因为 JIT 编译器需要第一次发挥它的魔力。 .NET 4.6 有一个全新的 x64 抖动(项目 RyuJIT),你不会得到与以前的 .NET 版本可比的结果。报告严重性能下降的最佳方法是使用 connect.microsoft.com,快点,而 4.6 仍处于测试阶段。 事实上 4.6 是 rc 而不是 beta。这是关于 microsoft connect 的报告:“connect.microsoft.com/VisualStudio/feedback/details/1294384”。 【参考方案1】:

性能回归的原因在GitHub上回答;简而言之,它似乎只在 Intel 而不是在 Amd64 机器上重现。内循环操作

Av[i] += v[j] * A(i, j);

结果

IN002a: 000093 lea      eax, [rax+r10+1]
IN002b: 000098 cvtsi2sd xmm1, rax
IN002c: 00009C movsd    xmm2, qword ptr [@RWD00]
IN002d: 0000A4 divsd    xmm2, xmm1
IN002e: 0000A8 movsxd   eax, edi
IN002f: 0000AB movaps   xmm1, xmm2
IN0030: 0000AE mulsd    xmm1, qword ptr [r8+8*rax+16]
IN0031: 0000B5 addsd    xmm0, xmm1
IN0032: 0000B9 movsd    qword ptr [rbx], xmm0

Cvtsi2sd 对未修改的 xmm 寄存器的高字节进行部分写入低 8 字节。对于复制案例,xmm1 是部分编写的,但在代码中还有 xmm1 的进一步用途。这会在 cvtsi2sd 和使用 xmm1 的其他指令之间产生错误的依赖性,从而影响指令并行性。实际上,在 cvtsi2sd 修复性能回归之前,将 Int 的 codegen 修改为 Float cast 以发出“xorps xmm1, xmm1”。

解决方法:如果我们在 MultiplyAv/MultiplyAvt 方法中反转乘法运算中的操作数顺序,也可以避免性能回归

void MultiplyAv(int n, double[] v, double[] Av)

    for (int i = 0; i < n; i++)
    
        Av[i] = 0;
        for (int j = 0; j < n; j++)  
              Av[i] += v[j] * A(i, j);  //  order of operands reversed
    

【讨论】:

以上是关于.NET 4.6 RC x64 的速度是 x86 的两倍(发行版)的主要内容,如果未能解决你的问题,请参考以下文章

从 x64 .NET 访问 x86 COM

为啥编译为 x86 的 .NET EXE 会以 x64 运行?

在 ASP.NET 应用程序的生产环境中将 x86 更改为 x64 dll

x86、x32 和 x64 架构之间的区别?

测试篇 c#读PE文件x86和x64

在“任何 CPU”.NET 程序集上强制 x86 CLR