В ответ на мой вопрос Преимущества использования 32-битных регистров / инструкций в x86-64 я начал измерять стоимость инструкций. Я знаю, что это делалось несколько раз (например, Agner Fog), но я делаю это для развлечения и самообразования.
Мой тестовый код довольно прост (для простоты здесь как псевдокод, на самом деле на ассемблере):
for(outer_loop=0; outer_loop<NO;outer_loop++){
operation #first
operation #second
...
operation #NI-th
}
Но все же некоторые вещи следует учитывать.
- Если внутренняя часть цикла велика (большой
NI>10^7
), все содержимое цикла не помещается в кэш инструкций и, следовательно, должно загружаться снова и снова, в результате чего скорость ОЗУ определяет время, необходимое для выполнения. Например, для больших внутренних частейxorl %eax, %eax
(2 байта) на 33% быстрее, чемxorq %rax, %rax
(3 байта). - Если
NI
мало и весь цикл легко помещается в кэш инструкций, тоxorl %eax, %eax
иxorq %rax, %rax
одинаково быстры и могут выполняться 4 раза за такт.
Однако эта простая модель не выдерживает критики jmp
-инструкции. Для jmp
-инструкции мой тестовый код выглядит следующим образом:
for(outer_loop=0; outer_loop<NO;outer_loop++){
jmp .L0
.L0: jmp .L1
L1: jmp L2
....
}
И вот результаты:
- Для "больших" размеров цикла (уже для
NI>10^4
) я измеряю 4,2 нс /jmp
-инструкцию (это равняется 42 байтам, загруженным из ОЗУ, или примерно 12 тактам на моей машине). - Для небольших размеров цикла (
NI<10^3
) я измеряю 1 нс /jmp-
инструкцию (что составляет около 3 тактовых циклов, что звучит правдоподобно - таблицы Агнера Фога показывают стоимость 2 тактовых циклов).
Инструкция jmp LX
использует 2-байтовую eb 00
кодировку.
Итак, мой вопрос: Чем может быть объяснение дороговизны jmp
-инструкции в "больших" циклах?
PS: Если вы хотите попробовать его на своем компьютере, вы можете загрузить сценарии с здесь, просто запустите sh jmp_test.sh
в папке src.
Изменить: экспериментальные результаты, подтверждающие теорию размера BTB Питера.
В следующей таблице показаны циклы на инструкцию для различных значений ǸI
(относительно NI
= 1000):
|oprations/ NI | 1000 | 2000| 3000| 4000| 5000| 10000|
|---------------------|------|------|------|------|------|------|
|jmp | 1.0 | 1.0 | 1.0 | 1.2 | 1.9 | 3.8|
|jmp+xor | 1.0 | 1.2 | 1.3 | 1.6 | 2.8 | 5.3|
|jmp+cmp+je (jump) | 1.0 | 1.5 | 4.0 | 4.4 | 5.5 | 5.5|
|jmp+cmp+je (no jump) | 1.0 | 1.2 | 1.3 | 1.5 | 3.8 | 7.6|
Это можно увидеть:
- Для инструкции
jmp
(пока неизвестный) ресурс становится дефицитным, и это приводит к снижению производительности дляǸI
больше 4000. - Этот ресурс не используется совместно с такими инструкциями, как
xor
- снижение производительности продолжается дляNI
около 4000, еслиjmp
иxor
выполняются друг за другом. - Но этот ресурс используется совместно с
je
, если скачок сделан - на _29 _ + _ 30_ один за другим ресурс становится дефицитным наNI
около 2000. - Однако, если
je
вообще не прыгает, ресурс снова становится дефицитным, посколькуNI
составляет около 4000 (4-я строка).
В статьях Мэтта Годболта о реверс-инжиниринге предсказания ветвлений установлено, что целевая буферная емкость ветвления составляет 4096 записи. Это очень убедительное свидетельство того, что промахи BTB являются причиной наблюдаемой разницы в пропускной способности между маленькими и большими jmp
петлями.
xorq %rax,%rax
делает то же самое, что иxorl %eax,%eax
, поэтому почти никогда не бывает причин использовать первое (за исключением, возможно, того, чтобы избежать необходимости вставлятьnop
для выравнивания где-нибудь). - person fuz   schedule 07.08.2016mov
иxor
мне нужно пройти до 10 ^ 7 инструкций в цикле, чтобы увидеть скорость RAM. Однакоjmp
становится в 4 раза медленнее с 10 ^ 3 до 10 ^ 4. Я не говорю, что это из-за ОЗУ - это что-то другое, но я не совсем понимаю, что это такое. - person ead   schedule 07.08.2016jmp+cmp+je (no jump)
случай не затрагивает дефицит ресурсов до примерно 4000 прыжков, заключается в том, что прыжки, которые не выполняются, не выполняются t потребляют запись BTB (действительно, вставлять в BTB было бы нечего!). - person BeeOnRope   schedule 09.08.2016