Фон:
При оптимизации некоторого Pascal кода со встроенным языком ассемблера я заметил ненужную инструкцию MOV и удалил ее.
К моему удивлению, удаление ненужной команды заставило мою программу замедлить работу.
Я обнаружил, что  добавление произвольных, бесполезных команд MOV увеличивает производительность еще больше.
Эффект неустойчивый и изменения, основанные на порядке выполнения: те же самые старшие инструкции, перенесенные на вверх или вниз одной строкой , создают замедление.
Я понимаю, что процессор делает всевозможные оптимизации и оптимизацию, но это больше похоже на черную магию.
Данные:
Версия моего кода условно компилирует  три нежелательных операции в середине цикла, который запускает 2**20==1048576 раз. (Окружающая программа просто вычисляет хэши SHA-256.
Результаты на моей довольно старой машине (Intel (R) Core (TM) 2 CPU 6400 @2.13 GHz):
avg time (ms) with -dJUNKOPS: 1822.84 ms
avg time (ms) without:        1836.44 ms
Программы выполнялись 25 раз в цикле, причем порядок выполнения каждый раз изменялся случайным образом.
Выдержки:
{$asmmode intel}
procedure example_junkop_in_sha256;
  var s1, t2 : uint32;
  begin
    // Here are parts of the SHA-256 algorithm, in Pascal:
    // s0 {r10d} := ror(a, 2) xor ror(a, 13) xor ror(a, 22)
    // s1 {r11d} := ror(e, 6) xor ror(e, 11) xor ror(e, 25)
    // Here is how I translated them (side by side to show symmetry):
  asm
    MOV r8d, a                 ; MOV r9d, e
    ROR r8d, 2                 ; ROR r9d, 6
    MOV r10d, r8d              ; MOV r11d, r9d
    ROR r8d, 11    {13 total}  ; ROR r9d, 5     {11 total}
    XOR r10d, r8d              ; XOR r11d, r9d
    ROR r8d, 9     {22 total}  ; ROR r9d, 14    {25 total}
    XOR r10d, r8d              ; XOR r11d, r9d
    // Here is the extraneous operation that I removed, causing a speedup
    // s1 is the uint32 variable declared at the start of the Pascal code.
    //
    // I had cleaned up the code, so I no longer needed this variable, and 
    // could just leave the value sitting in the r11d register until I needed
    // it again later.
    //
    // Since copying to RAM seemed like a waste, I removed the instruction, 
    // only to discover that the code ran slower without it.
    {$IFDEF JUNKOPS}
    MOV s1,  r11d
    {$ENDIF}
    // The next part of the code just moves on to another part of SHA-256,
    // maj { r12d } := (a and b) xor (a and c) xor (b and c)
    mov r8d,  a
    mov r9d,  b
    mov r13d, r9d // Set aside a copy of b
    and r9d,  r8d
    mov r12d, c
    and r8d, r12d  { a and c }
    xor r9d, r8d
    and r12d, r13d { c and b }
    xor r12d, r9d
    // Copying the calculated value to the same s1 variable is another speedup.
    // As far as I can tell, it doesn't actually matter what register is copied,
    // but moving this line up or down makes a huge difference.
    {$IFDEF JUNKOPS}
    MOV s1,  r9d // after mov r12d, c
    {$ENDIF}
    // And here is where the two calculated values above are actually used:
    // T2 {r12d} := S0 {r10d} + Maj {r12d};
    ADD r12d, r10d
    MOV T2, r12d
  end
end;
Попробуйте сами:
Код находится в режиме онлайн в GitHub, если вы хотите попробовать сами.
Мои вопросы:
- Почему бесполезное копирование содержимого регистра в RAM повышает производительность?
- Почему одна и та же бесполезная инструкция обеспечивает ускорение на некоторых строках и замедление на других?
- Является ли это поведение чем-то, что может быть предсказано компилятором?
