Подтвердить что ты не робот

Какие подсказки по оптимизации можно дать компилятору/JIT?

Я уже профилировал, и теперь я хочу сжать все возможные возможности производительности из моей горячей точки.

Я знаю о [MethodImplOptions.AggressiveInlining] и ProfileOptimization класс. Есть ли другие?


[Изменить] Я только что обнаружил [TargetedPatchingOptOut]. Nevermind, по-видимому что он не нужен.

4b9b3361

Ответ 1

Вы исчерпали параметры, добавленные в .NET 4.5, чтобы напрямую влиять на jitted-код. Следующий шаг - посмотреть на сгенерированный машинный код, чтобы выявить любую очевидную неэффективность. Сделайте это с помощью отладчика, прежде всего, чтобы он не отключил оптимизатор. Tools + Options, Debugging, General, отключите опцию "Подавить оптимизацию JIT при загрузке модуля". Установите точку останова в горячем коде, Debug + Disassembly, чтобы посмотреть на него.

Не так уж много, но оптимизатор дрожания в целом отлично справляется. Одна вещь, которую нужно искать - это неудачные попытки устранить проверку границ массива, ключевое слово fixed - это небезопасное обходное решение для этого. Угловой случай - неудачная попытка встраивания метода, и джиттер, не использующий регистры процессора, эффективно, проблема с джиттером x86 и исправлена ​​с помощью метода MethodImplOptions.NoInlining. Оптимизатор не очень эффективен при вытаскивании инвариантного кода из цикла, но это то, что вы почти всегда должны учитывать при просмотре кода С# при поиске способов его оптимизации.

Самое главное, что нужно знать, это когда вы закончите и просто не можете надеяться сделать это быстрее. Вы можете реально получить там, сравнивая яблоки и апельсины и записывая горячий код в собственный код с помощью С++/CLI. Убедитесь, что этот код скомпилирован С#pragma неуправляемым, поэтому он получает полную любовь оптимизатора. Там затраты, связанные с переключением с управляемого кода на выполнение собственного кода, так что убедитесь, что время выполнения собственного кода достаточно существенное. Это, в противном случае, не всегда легко сделать, и у вас наверняка не будет гарантии успеха. Хотя знание о том, что вы сделано, может сэкономить вам много времени, спотыкаясь о мертвые переулки.

Ответ 2

Да, есть больше трюков: -)

На самом деле я провел довольно много исследований по оптимизации кода С#. До сих пор это наиболее значимые результаты:

  • Func и Action, которые передаются напрямую, часто устанавливаются JIT'ter. Обратите внимание, что вы не должны хранить их как переменные, потому что они затем называются делегатами. См. Также этот пост для более подробной информации.
  • Будьте осторожны с перегрузками. Вызов равных без использования IEquatable<T> обычно является плохим планом, поэтому если вы используете f.ex. хеш, не забудьте реализовать правильные перегрузки и интерфейсы, потому что он будет защищать вас от тонны производительности.
  • Генерики, вызванные из других классов, никогда не привязаны. Причиной этого является "волшебство", обозначенное здесь.
  • Если вы используете структуру данных, обязательно попробуйте использовать массив:-) На самом деле, эти вещи быстрей, как черт по сравнению с... ну, почти все, что я предполагаю. Я оптимизировал довольно много вещей, используя собственные хэш-таблицы и используя массивы вместо списка.
  • Во многих случаях поиск таблиц выполняется быстрее, чем вычисление вещей или использование таких конструкций, как vtable lookups, switch, multiple if statements и даже вычисления. Это также хороший трюк, если у вас есть ветки; неудачное предсказание ветки часто может стать большой болью. См. Также этот пост - это трюк, который я использую довольно много в С#, и он отлично работает во многих случаях. О, и таблицы поиска - это, конечно, массивы.
  • Эксперимент с созданиями (малых) классов. Из-за природы типов значений некоторые оптимизации различаются для структуры, чем для классов. Например, вызовы методов проще, поскольку компилятор точно знает, какой метод будет вызван. Кроме того, массивы структур обычно быстрее, чем массивы классов, поскольку они требуют 1 операции памяти меньше для операции с массивом.
  • Не используйте многомерные массивы. Хотя я предпочитаю Foo[], даже Foo[][] обычно быстрее, чем Foo[,].
  • Если вы копируете данные, предпочитайте Buffer.BlockCopy через Array.Copy в любой день недели. Также будьте осторожны вокруг строк: струнные операции могут быть сливками производительности.

Там также использовалось руководство под названием "оптимизация для процессора intel pentium" с большим количеством трюков (например, смены или умножения вместо деления). Хотя компилятор делает тонкие усилия в настоящее время, это также иногда помогает немного.

Конечно, это просто оптимизация; наибольший прирост производительности обычно является результатом изменения алгоритма и/или структуры данных. Не забудьте проверить, какие опции доступны вам и не слишком сильно ограничивают .NET framework... Также у меня есть естественная тенденция недоверия к реализации .NET, пока я не проверил декомпилированный код сам... там тонна вещей, которые могли бы быть реализованы намного быстрее (в большинстве случаев по уважительным причинам).

НТН


Алекс указал мне, что Array.Copy на самом деле быстрее, по мнению некоторых людей. И поскольку я действительно не знаю, что изменилось за эти годы, я решил, что единственный правильный курс действий - создать новый новый тест и испытать его.

Если вас просто интересуют результаты, опустите. В большинстве случаев вызов Buffer.BlockCopy явно превосходит Array.Copy. Протестировано на Intel Skylake с 16-гигабайтной памятью ( > 10 ГБ бесплатно) на .NET 4.5.2.

код:

static void TestNonOverlapped1(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K];
    byte[] tmp2 = new byte[K];
    for (long i = 0; i < iter; ++i)
    {
        Array.Copy(tmp, tmp2, K);
    }
}

static void TestNonOverlapped2(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K];
    byte[] tmp2 = new byte[K];
    for (long i = 0; i < iter; ++i)
    {
        Buffer.BlockCopy(tmp, 0, tmp2, 0, K);
    }
}

static void TestOverlapped1(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K + 16];
    for (long i = 0; i < iter; ++i)
    {
        Array.Copy(tmp, 0, tmp, 16, K);
    }
}

static void TestOverlapped2(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K + 16];
    for (long i = 0; i < iter; ++i)
    {
        Buffer.BlockCopy(tmp, 0, tmp, 16, K);
    }
}

static void Main(string[] args)
{
    for (int i = 0; i < 10; ++i)
    {
        int N = 16 << i;

        Console.WriteLine("Block size: {0} bytes", N);

        Stopwatch sw = Stopwatch.StartNew();

        {
            sw.Restart();
            TestNonOverlapped1(N);

            Console.WriteLine("Non-overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestNonOverlapped2(N);

            Console.WriteLine("Non-overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestOverlapped1(N);

            Console.WriteLine("Overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestOverlapped2(N);

            Console.WriteLine("Overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        Console.WriteLine("-------------------------");
    }

    Console.ReadLine();
}

Результаты на x86 JIT:

Block size: 16 bytes
Non-overlapped Array.Copy: 4267.52 ms
Non-overlapped Buffer.BlockCopy: 2887.05 ms
Overlapped Array.Copy: 3305.01 ms
Overlapped Buffer.BlockCopy: 2670.18 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 1327.55 ms
Non-overlapped Buffer.BlockCopy: 763.89 ms
Overlapped Array.Copy: 2334.91 ms
Overlapped Buffer.BlockCopy: 2158.49 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 705.76 ms
Non-overlapped Buffer.BlockCopy: 390.63 ms
Overlapped Array.Copy: 1303.00 ms
Overlapped Buffer.BlockCopy: 1103.89 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 361.18 ms
Non-overlapped Buffer.BlockCopy: 219.77 ms
Overlapped Array.Copy: 620.21 ms
Overlapped Buffer.BlockCopy: 577.20 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 192.92 ms
Non-overlapped Buffer.BlockCopy: 108.71 ms
Overlapped Array.Copy: 347.63 ms
Overlapped Buffer.BlockCopy: 353.40 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 104.69 ms
Non-overlapped Buffer.BlockCopy: 65.65 ms
Overlapped Array.Copy: 211.77 ms
Overlapped Buffer.BlockCopy: 202.94 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 52.93 ms
Non-overlapped Buffer.BlockCopy: 38.84 ms
Overlapped Array.Copy: 144.39 ms
Overlapped Buffer.BlockCopy: 154.09 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 45.64 ms
Non-overlapped Buffer.BlockCopy: 30.11 ms
Overlapped Array.Copy: 118.33 ms
Overlapped Buffer.BlockCopy: 109.16 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 30.93 ms
Non-overlapped Buffer.BlockCopy: 30.72 ms
Overlapped Array.Copy: 119.73 ms
Overlapped Buffer.BlockCopy: 104.66 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 30.37 ms
Non-overlapped Buffer.BlockCopy: 26.63 ms
Overlapped Array.Copy: 90.46 ms
Overlapped Buffer.BlockCopy: 87.40 ms
-------------------------

Результаты на x64 JIT:

Block size: 16 bytes
Non-overlapped Array.Copy: 1252.71 ms
Non-overlapped Buffer.BlockCopy: 694.34 ms
Overlapped Array.Copy: 701.27 ms
Overlapped Buffer.BlockCopy: 573.34 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 995.47 ms
Non-overlapped Buffer.BlockCopy: 654.70 ms
Overlapped Array.Copy: 398.48 ms
Overlapped Buffer.BlockCopy: 336.86 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 498.86 ms
Non-overlapped Buffer.BlockCopy: 329.15 ms
Overlapped Array.Copy: 218.43 ms
Overlapped Buffer.BlockCopy: 179.95 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 263.00 ms
Non-overlapped Buffer.BlockCopy: 196.71 ms
Overlapped Array.Copy: 137.21 ms
Overlapped Buffer.BlockCopy: 107.02 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 144.31 ms
Non-overlapped Buffer.BlockCopy: 101.23 ms
Overlapped Array.Copy: 85.49 ms
Overlapped Buffer.BlockCopy: 69.30 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 76.76 ms
Non-overlapped Buffer.BlockCopy: 55.31 ms
Overlapped Array.Copy: 61.99 ms
Overlapped Buffer.BlockCopy: 54.06 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 44.01 ms
Non-overlapped Buffer.BlockCopy: 33.30 ms
Overlapped Array.Copy: 53.13 ms
Overlapped Buffer.BlockCopy: 51.36 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 27.05 ms
Non-overlapped Buffer.BlockCopy: 25.57 ms
Overlapped Array.Copy: 46.86 ms
Overlapped Buffer.BlockCopy: 47.83 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 29.11 ms
Non-overlapped Buffer.BlockCopy: 25.12 ms
Overlapped Array.Copy: 45.05 ms
Overlapped Buffer.BlockCopy: 47.84 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 24.95 ms
Non-overlapped Buffer.BlockCopy: 21.52 ms
Overlapped Array.Copy: 43.81 ms
Overlapped Buffer.BlockCopy: 43.22 ms
-------------------------