Ответ 1
Вы не выровняли свой цикл.
Если вся ваша команда перехода не находится в той же строке кэша, что и остальная часть цикла, вы получаете дополнительный цикл для извлечения следующей строки кэша.
Различные альтернативы, которые вы указали, собраны в следующие кодировки.
0: ff 04 1c inc DWORD PTR [esp+ebx*1]
3: ff 04 24 inc DWORD PTR [esp]
6: ff 44 24 08 inc DWORD PTR [esp+0x8]
[esp]
и [esp+reg]
оба кодируются в 3 байта, [esp+8]
занимает 4 байта.
Поскольку цикл начинается в некотором случайном месте, дополнительный байт выталкивает (часть) инструкцию jne loop
в следующую строку кэша.
Линия кэша обычно составляет 16 байт.
Вы можете решить эту проблему, переписав код следующим образом:
mov eax, 0
mov ebx, 8
.align 16 ;align on a cache line.
loop:
inc dword ptr [esp + ebx] ;7 cycles
inc eax ;0 latency drowned out by inc [mem]
cmp eax, 0xFFFFFFFF ;0 " "
jne loop ;0 " "
mov eax, 1
mov ebx, 0
int 0x80
Этот цикл должен принимать 7 циклов на итерацию.
Не обращая внимания на то, что цикл не выполняет никакой полезной работы, его можно оптимизировать следующим образом:
mov eax, 1 ;start counting at 1
mov ebx, [esp+ebx]
.align 16
loop: ;latency ;comment
lea ebx,[ebx+1] ; 0 ;Runs in parallel with `add`
add eax,1 ; 1 ;count until eax overflows
mov [esp+8],ebx ; 0 ;replace a R/W instruction with a W-only instruction
jnc loop ; 1 ;runs in parallel with `mov [mem],reg`
mov eax, 1
xor ebx, ebx
int 0x80
Этот цикл должен принимать 2 цикла на итерацию.
Заменив inc eax
на add
и заменив inc [esp]
инструкциями, которые не изменяют флаги, вы позволяете процессору параллельно выполнять инструкции lea + mov
и add+jmp
. add
может быть быстрее на более новом процессоре, потому что add
изменяет флаги all, тогда как inc
изменяет только подмножество флагов.
Это может привести к задержке частичного регистра в команде jxx
, поскольку она должна дождаться разрешения частичной записи в регистр флагов.
mov [esp]
также быстрее, потому что вы не выполняете цикл read-modify-write
, вы только записываете в память внутри цикла.
Дальнейшие выигрыши могут быть достигнуты путем разворачивания цикла, но выигрыш будет небольшим, потому что доступ к памяти здесь доминирует над временем выполнения, и для этого начинается глупая петля.
Подводя итог:
- Избегайте инструкций Read-modify-write в цикле, попробуйте заменить их отдельными инструкциями для чтения, изменения и записи или переместите чтение/запись вне цикла.
- Избегайте
inc
для управления счетчиками циклов, вместо этого используйтеadd
. - Попробуйте использовать
lea
для добавления, когда вас не интересуют флаги. - Всегда выравнивайте небольшие циклы в строках кэша
.align 16
. - Не используйте
cmp
внутри цикла, командаinc/add
уже изменяет флаги.