.NET 4.6 RC x64 в два раза медленнее, чем x86 (версия выпуска)

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?

UPDATE

Оказывается, RyuJIT играет определенную роль в этой проблеме. Если параметр useLegacyJit включен в app.config, результат будет другим, и на этот раз 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>

UPDATE

Теперь проблема была сообщена команде CLR coreclr, выпуск 993

Ответ 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 выполняет частичную запись нижних 8-байтов с верхними байтами регистра xmm без изменений. Для случая воспроизведения xmm1 частично записано, но дальнейшее использование xmm1 вниз по коду. Это создает ложную зависимость между cvtsi2sd и другими инструкциями, которые используют xmm1, который влияет на инструкцию parallelism. Действительно, модифицируя codegen от Int до Float cast, чтобы испускать "xorps xmm1, xmm1", прежде чем cvtsi2sd исправляет первичную регрессию.

Обходной путь: регрессия Perf также можно избежать, если мы изменим порядок операндов при многократной работе в методах 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
    }
}