Как первый из этих двух фрагментов кода может работать в 3 раза быстрее второго, если он выполняет больше работы? - PullRequest
6 голосов
/ 05 мая 2011

Как можно этот код:

var check = 0;

for (var numerator = 0; numerator <= maxNumerator; numerator++)
{
    check += numerator >= 0
           ? numerator - (int) ((numerator * qdi.Multiplier) >> qdi.Shift) * qdi.Number
           : numerator - (int) -((-numerator * qdi.Multiplier) >> qdi.Shift) * qdi.Number;
}

return check;

работает в 3 раза быстрее, чем этот код:

var check = 0;

for (var numerator = 0; numerator <= maxNumerator; numerator++)
{
    check += numerator >= 0
           ? (int) ((numerator * qdi.Multiplier) >> qdi.Shift)
           : (int) -((-numerator * qdi.Multiplier) >> qdi.Shift);
}

return check;

Первый фрагмент кода выполняет точно такую ​​же операцию быстрого деления (то есть умножение, затем сдвиг вправо), но также вычитание и умножение, но JIT-компилятор, похоже, производит более медленный код.

У меня есть код разборки для каждого из доступных.
Более медленный код выталкивает регистр rbx и вычитает 10h из rsp в начале, а затем добавляет его обратно и выдает rbx в конце, тогда как более быстрые коды этого не делают.
Более медленный код также использует регистр r11 для большинства вещей, где более быстрый код использует rdx.

Есть идеи?

Ответы [ 2 ]

1 голос
/ 05 мая 2011

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

Также может показаться, что троичный вариант может генерировать менее эффективный код, чем простой if / else.

Таким образом, изменяя код цикла во втором фрагменте на:

if (numerator >= 0) check += (int) ((numerator * qdi.Multiplier) >> qdi.Shift);
else check += (int) -((-numerator * qdi.Multiplier) >> qdi.Shift);

или:

if (numerator < 0) check += (int) -((-numerator * qdi.Multiplier) >> qdi.Shift);
else check += (int) ((numerator * qdi.Multiplier) >> qdi.Shift);

или:

check += numerator < 0
    ? (int) -((-numerator * qdi.Multiplier) >> qdi.Shift)
    : (int) ((numerator * qdi.Multiplier) >> qdi.Shift);

приведет к более быстрому выполнению кода.

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

0 голосов
/ 06 мая 2011

Как вы измеряете?Ваше описание языка ассемблера не похоже на то, что имело бы огромную разницу в производительности.

В любом случае, если это действительно намного медленнее, я сомневаюсь, что кто-либо из команды JIT может точно сказать, почему это происходит.Я заметил, что при работе с микробенчами .NET, казалось бы, тривиальные изменения кода могут сделать код значительно быстрее или медленнее.Если вы можете сделать этот код (который вызывает медлительность) настолько простым, насколько это возможно, вы можете пожаловаться MS на него в Microsoft Connect.

Вы можете попробовать скопировать qdi.Multiplier, qdi.Shift и qdi.Number (что угодноони) к локальным переменным, что иногда помогает.

...