Если у меня есть 8-битное значение, есть ли преимущество в использовании 8-битного регистра вместо, скажем, 16, 32 или 64-битного? - PullRequest
0 голосов
/ 30 ноября 2018

Вступительная литература по x86 asm, которую я читаю, похоже, придерживается 32-битных регистров (eax, ebx и т. Д.) Во всех практических сценариях, за исключением демонстрации 64-битных регистров как вещи, которая также существует.Если вообще упоминаются 16-битные регистры, это как историческая справка, объясняющая, почему 32-битные регистры имеют «e» перед своими именами.Компиляторы, кажется, одинаково не заинтересованы в менее чем 32-битных регистрах.

Рассмотрим следующий код C:

int main(void) { return 511; }

Хотя main подразумевает возвращение int, фактически, состояние выхода Linuxкоды являются 8-битными, то есть любое значение свыше 255 будет наименее значимым 8-битным, а именно

hc027@HC027:~$ echo "int main(void) { return 511; }" > exit_gcc.c
hc027@HC027:~$ gcc exit_gcc.c 
hc027@HC027:~$ ./a.out 
hc027@HC027:~$ echo $?
255

Итак, мы видим, что только первые 8-биты возвращаемого значения int main(void) будутбыть использованы системой. И все же когда мы попросим GCC о выводе на ассемблере той же программы, сохранит ли он возвращаемое значение в 8-битном регистре?Давайте узнаем!

hc027@HC027:~$ cat exit_gcc.s
    .file   "exit_gcc.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $511, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits

Нет!Он использует% eax, очень-32-битный регистр!Теперь GCC умнее меня, и, возможно, возвращаемое значение int main(void) используется для других вещей, которые не знают, где его возвращаемое значение, не будет усечено до 8 младших значащих бит (или, может быть, стандарт С постановляет, что он должен возвращать a для реального, фактического int независимо от его фактической судьбы)

Но независимо от эффективности моего конкретного примера, вопрос остается в силе.Насколько я могу судить, современные 32-битные программисты и компиляторы пренебрегают регистрами в 32-битной среде.Краткий гугл «когда использовать 16-битные регистры x86» не дает соответствующих ответов.Мне довольно любопытно: есть ли преимущество использования 8- и 16-разрядных регистров в процессорах x86?

Ответы [ 2 ]

0 голосов
/ 04 декабря 2018

Существует два практических применения int8_t и uint8_t.Это экономит память, что важно не потому, что основной компьютер закончится, а потому, что позволяет большему количеству данных помещаться в кэш вашего процессора.И иногда вам также нужно точно указать свой макет в памяти, например, для драйвера устройства или заголовка пакета.

Сами инструкции не быстрее (как показывает замечательный ответ Николаса Пипитона) и могут потребовать больше или меньше байтовкодировать.В некоторых случаях вы можете улучшить распределение регистра.

0 голосов
/ 01 декабря 2018

Здесь происходит немного истории.Попробуйте запустить

mov rax, -1
mov eax, 0
print rax

на вашем любимом рабочем столе x86 (print в зависимости от вашей среды).Вы заметите, что, хотя rax начинался со всех, и вы думаете, что уничтожили только 32 нижних бита, оператор print выводит ноль!Пишет eax полностью стереть rax.Зачем?Потому что это намного быстрее.Попытка сохранить более высокие значения rax является абсолютной болью, когда вы продолжаете писать в eax.

Intel / AMD не помнили об этом, когда решили перейти на 32-разрядную версию, и сделалиошибка, из-за которой al / ah не использовался за пределами загрузчиков и других битовых фиддлеров: когда вы пишете в al или ah, другой не забивается!Это было здорово в 16-битную эпоху, потому что теперь у вас в два раза больше регистров, и , у вас есть 32-битный регистр!Но с переходом на изобилие регистров мы больше не хотим больше регистров.Нам нужны быстрые регистры и больше ГГц.С этой точки зрения, каждый раз, когда вы пишете в al или ah, вам все равно придется читать из eax.Это создает большую нагрузку на испорченный исполнитель.

Достаточно теории, давайте проведем несколько реальных тестов.Каждый был проверен три раза.Эти тесты выполнялись в Intel Core i5-4278U CPU @ 2.60GHz

Время: 1,067 с, 1,072 с, 1,097 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov rax, 5
mov rax, 5
mov rax, 6
mov rax, 6
mov rax, 7
mov rax, 7
mov rax, 8
mov rax, 8
dec ecx
jmp loop
exit:
ret

Время: 1,072 с, 1,062 с, 1,060 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov eax, 5
mov eax, 5
mov eax, 6
mov eax, 6
mov eax, 7
mov eax, 7
mov eax, 8
mov eax, 8
dec ecx
jmp loop
exit:
ret

Время: 2.702 с, 2.748 с, 2.704 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov ah, 5
mov ah, 5
mov ah, 6
mov ah, 6
mov ah, 7
mov ah, 7
mov ah, 8
mov ah, 8
dec ecx
jmp loop
exit:
ret

Время: 1.432 с, 1.457 с, 1.427 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov ah, 5
mov al, 5
mov ah, 6
mov al, 6
mov ah, 7
mov al, 7
mov ah, 8
mov al, 8
dec ecx
jmp loop
exit:
ret

Время: 1.117 с, 1.084 с, 1.082s

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov ah, 5
mov al, 5
mov eax, 6
mov al, 6
mov ah, 7
mov eax, 7
mov ah, 8
mov al, 8
dec ecx
jmp loop
exit:
ret

Эти тесты не связаны с частичной остановкой регистра, так как я не читаю eax после записи в ah.Это просто стоимость работы с 8 или 16 битами, когда ваша шина 32 или 64 бит.В моем случае шина моего процессора 64-битная.32-битные записи не пострадали, так как это действительно 64-битная запись.Есть неявные 32 бита нулей, которые добавляются к каждой записи в eax.Это важно, так как большая часть кода скомпилирована в x86, и это должно быть быстродействующим в системах x86_64.

Кроме того, если вы захотите попробовать, вы заметите, что add eax, 5 и add ah, 5 оба принимаютстолько же времени (2,7 с на моем процессоре, столько же, сколько mov ah, 5).В этом случае вам все равно придется читать с eax, поэтому нет никакой разницы.Разница в том, что mov ah, 5 не должно требовать чтения, но это все же требуется.Это то, чем mov eax, 5 извлекает выгоду, но ah не может.

В тесте подстановки ah / al мы видим, что переименование регистров, вероятно, помогает со всеми "mov ah, 5;Мов ал, 5 "пишет.Похоже, что «ах» и «ал» имеют свои собственные регистры для работы, и их можно затем сделать параллельными, что значительно экономит время.С тестом ah / al / eax он был почти таким же быстрым, как тест eax!В этом случае я предсказываю, что все три получили свои собственные регистры, и код был сильно распараллелен, даже когда отдельные записи в ах / ал были дорогими.Конечно, попытка прочитать eax в любом месте этого цикла приведет к снижению производительности, когда необходимо объединить ах / ал:

Время: 3,412 с, 3,390 с, 3,515 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov ah, 5
mov al, 5
xor eax, 5
mov al, 6
mov ah, 8
xor eax, 5
mov al, 8
dec ecx
jmp loop
exit:
ret

Вышеупомянутый тест не имеет контрольной группы, так как он использует xor вместо mov (что, если просто использовать «xor», является причиной, почему он медленный).Итак, вот тест для сравнения:

Время: 1,426 с, 1,424 с, 1,392 с

global _main
_main:
mov ecx, 1000000000
loop:
test ecx, ecx
jz exit
mov ah, 5
mov al, 5
xor ah, 5
mov al, 6
mov ah, 8
xor ah, 5
mov al, 8
dec ecx
jmp loop
exit:
ret

Последние два теста показывают частичную остановку регистра, которую я даже не рассматривал вначале.Сначала я подумал, что переименование регистра поможет смягчить проблему, что они определенно делают в миксах ах / ал и мих ах / ал / эакс.Тем не менее, чтение по eax с грязными значениями ah / al является жестоким, поскольку процессор теперь должен объединять регистры ah / al.Похоже, что производители процессоров считали, что переименование регистров частичных регистров все же стоило того, что имеет смысл, так как большая часть работы с ah / al не включает чтение в eax.Таким образом, узкие циклы, которые немного ломаются от ах / ал, значительно выигрывают, и единственный вред - сбой при следующем использовании eax (в этот момент ах / ал, вероятно, больше не будет использоваться).

В целом, даже в случае отсутствия частичного останова регистра записи в ah выполняются намного медленнее, чем записи в eax, что я и пытался донести.

Конечно, результаты могутварьироваться.Другие процессоры (скорее всего, очень старые) могут иметь управляющие биты для отключения половины шины, что позволит шине работать как 8-битная шина, когда это необходимо.Эти управляющие биты должны были бы быть соединены через логические вентили с регистрами (то есть подключены к флагу сброса триггеров), что значительно замедлило бы их, поскольку теперь это еще один выход, через который можно обновить регистр.Поскольку такие контрольные биты будут подавляющим большинством времени, похоже, что Intel решила этого не делать (по уважительной причине).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...