Усі наступні вказівки роблять те саме: встановлюємо %eax
нуль. Який спосіб є оптимальним (вимагає найменшої кількості машинних циклів)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Усі наступні вказівки роблять те саме: встановлюємо %eax
нуль. Який спосіб є оптимальним (вимагає найменшої кількості машинних циклів)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Відповіді:
TL; DR Резюме : xor same, same
є найкращим вибором для всіх процесорів . Жоден інший метод не має жодної переваги над ним, і він має принаймні деяку перевагу перед будь-яким іншим методом. Офіційно рекомендується Intel та AMD, і що робити компілятори. У 64-бітному режимі все ще використовуйте xor r32, r32
, оскільки запис 32-бітового регістру нульовий верхній 32 . xor r64, r64
є марною витратою байта, оскільки йому потрібен префікс REX.
Навіть гірше, що Сільвермонт визнає лише xor r32,r32
розбиттям, а не 64-бітовим розміром операнду. Таким чином, навіть коли префікс REX все-таки потрібен, оскільки ви нулюєте r8..r15, використовуйте xor r10d,r10d
, неxor r10,r10
.
GP-цілі приклади:
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
Нулювання векторного регістра, як правило, найкраще проводити за допомогою pxor xmm, xmm
. Це, як правило, те, що робить gcc (навіть перед використанням з інструкціями FP).
xorps xmm, xmm
може мати сенс. Він на один байт коротший pxor
, але йому xorps
потрібен порт виконання 5 на Intel Nehalem, при цьому він pxor
може працювати на будь-якому порті (0/1/5). (Затримка затримки байпасу 2c Негалема між цілим числом і FP зазвичай не має значення, оскільки виконання поза замовленням може зазвичай приховати його на початку нового ланцюга залежності).
У мікроархітектурах сімейства SnB жоден аромат xor-нулювання навіть не потребує порту виконання. На AMD і попередньо Nehalem P6 / Core2 Intel, xorps
і pxor
обробляються таким же чином (як вектор-цілочисельні інструкції).
Використання AVX-версії 128b векторної інструкції також нульовує верхню частину reg, тому vpxor xmm, xmm, xmm
це хороший вибір для занулення YMM (AVX1 / AVX2) або ZMM (AVX512), або будь-якого майбутнього векторного розширення. vpxor ymm, ymm, ymm
не бере зайвих байтів для кодування, хоча і працює те саме в Intel, але повільніше на AMD до Zen2 (2 упп). Занулення AVX512 ZMM вимагає додаткових байтів (для префікса EVEX), тому слід віддати перевагу нулю XMM або YMM.
Приклади XMM / YMM / ZMM
# Good:
xorps xmm0, xmm0 ; smallest code size (for non-AVX)
pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem.
xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# Good with AVX:
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
#sub-optimal AVX
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg
vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2
# Good with AVX512
vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions.
# Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
# sub-optimal with AVX512 (even without AVX512VL)
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
Див. Чи швидше занулення vxorps на AMD Jaguar / Bulldozer / Zen швидше з регістрами xmm, ніж ymm? і
який найефективніший спосіб очистити один чи декілька реєстрів ZMM на лицарській посадці?
Напівзалежно : найшвидший спосіб встановити значення __m256 для всіх ONE біт, а
встановити всі біти в регістрі процесора на 1 ефективно, також охоплює регістри k0..7
масок AVX512 . SSE / AVX vpcmpeqd
деагрегує на багатьох (хоча все-таки потрібен взагалі, щоб написати 1s), але AVX512 vpternlogd
для ZMM- регрів навіть не є зламним . Всередині циклу розгляньте можливість копіювання з іншого реєстру замість того, щоб створювати їх з ALU взагалі, особливо з AVX512.
Але нулювання коштує дешево: xor-нулювання регістра xmm всередині циклу, як правило, настільки ж добре, як і копіювання, за винятком деяких процесорів AMD (Bulldozer і Zen), які мають mov-усунення для векторних регрес, але все ще потребують ALU взагалі, щоб написати нулі для xor -зеронг.
Деякі процесори розпізнають sub same,same
як нульову ідіому xor
, але всі процесори, які розпізнають будь-які нульові ідіоми, розпізнаютьxor
. Просто використовуйте xor
так, що вам не доведеться турбуватися про те, який процесор визнає, яка нульова ідіома.
xor
(на відміну від ідентифікаційної нульової ідіоми mov reg, 0
) має деякі очевидні та деякі тонкі переваги (підсумковий список, тоді я розгорну на ці):
mov reg,0
. (Усі процесори)Менший розмір машинного коду (2 байти замість 5) - це завжди перевага: більша щільність коду призводить до меншої кількості пропусків кешу інструкцій, а також кращого вибору інструкцій та потенційного декодування смуги пропускання.
Перевага від використання блоку виконання для xor в мікроархітектурах сімейства Intel SnB незначна, але економить енергію. Це швидше має значення для SnB або IvB, у яких є лише 3 порту виконання ALU. Haswell та пізніше мають 4 порти виконання, які можуть обробляти цілі інструкції ALU, в тому числі mov r32, imm32
, завдяки ідеальному прийняттю рішення планувальником (що не завжди трапляється на практиці), HSW все ще може підтримувати 4 уопи за годину, навіть коли їм всі потрібні ALU порти виконання.
Дивіться мою відповідь на інше запитання про нульові регістри для отримання більш детальної інформації.
Блог Брюса Доусона, що пов’язаний Майклом Петчем (у коментарі до питання), зазначає, що xor
обробляється на етапі реєстрування-перейменування, не потребуючи блоку виконання (нуль упс у невключеному домені), але пропустив той факт, що це все-таки одна загальна у злитому домені. Сучасні процесори Intel можуть випускати та вилучати 4 Uops з плавленим доменом за годину. Ось звідки походить 4 нулі на тактову межу. Підвищена складність обладнання для перейменування реєстру є лише однією з причин обмеження ширини дизайну до 4. (Брюс написав дуже чудові публікації в блогах, як-от його серія з питань математики FP та x87 / SSE / округлення , що я роблю настійно рекомендую).
На AMD Bulldozer сімейство процесорів , mov immediate
працює на один і ті ж EX0 / EX1 порти виконання цілого , як xor
. mov reg,reg
може також працювати на AGU0 / 1, але це лише для копіювання реєстру, а не для встановлення з безпосередніх. Тож AFAIK, для AMD єдиною перевагою xor
над mov
є коротше кодування. Це також може зекономити фізичні ресурси, але я не бачив жодних тестів.
Визнані нульові ідіоми уникають покарань часткового реєстру на процесорах Intel, які перейменовують часткові регістри окремо від повних регістрів (сімейства P6 & SnB).
xor
буде позначати реєстр як нульові верхні частини , тому xor eax, eax
// inc al
/ inc eax
уникає звичайного покарання часткового реєстру, яке мають ЦП до IvB. Навіть без цього xor
IvB потребує об'єднання лише тоді, коли високі 8 біт ( AH
) змінені, і тоді зчитується весь реєстр, а Haswell навіть видаляє це.
З посібника з мікроарха Agner Fog, стор. 98 (розділ Pentium M, на який посилаються більш пізні розділи, включаючи SnB):
Процесор розпізнає XOR регістра з самим собою як встановлення його на нуль. Спеціальний тег у регістрі пам'ятає, що висока частина регістра дорівнює нулю, так що EAX = AL. Цей тег запам'ятовується навіть у циклі:
; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL
(від pg82): Процесор пам’ятає, що 24 верхніх бітів EAX дорівнюють нулю, доки ви не отримаєте перерви, помилки чи інші серіалізуючі події.
pg82 цього керівництва також підтверджує , що mov reg, 0
це НЕ визнаються в якості обнулення ідіоми, принаймні , на ранніх P6 конструкції , як PIII або PM. Я був би дуже здивований, якби вони витратили транзистори на їх виявлення на пізніших процесорах.
xor
встановлює прапори , а це означає, що вам потрібно бути обережними при тестуванні умов. Оскільки setcc
, на жаль, доступний лише для 8-бітового пункту призначення , вам зазвичай потрібно подбати про те, щоб уникнути часткових реєстраційних санкцій.
Було б добре, якби x86-64 перевстановив один із видалених опкодів (наприклад, AAM) для біта 16/32/64 setcc r/m
, при цьому предикат кодується у 3-бітовому полі джерела-регістра поля r / m (шлях) деякі інші інструкції з одним операндом використовують їх як біти коду). Але вони цього не робили, і це не допомогло б для x86-32 все одно.
В ідеалі вам слід використовувати xor
/ встановити прапори / setcc
/ прочитати повний реєстр:
...
call some_func
xor ecx,ecx ; zero *before* the test
test eax,eax
setnz cl ; cl = (some_func() != 0)
add ebx, ecx ; no partial-register penalty here
Це має оптимальну продуктивність для всіх процесорів (без стійлів, об'єднань Uops або помилкових залежностей).
Речі складніші, коли ви не хочете робити xor перед інструкцією з встановлення прапора . наприклад, ви хочете відгалужуватися на одній умові, а потім встановити іншу умову з тих же прапорів. наприклад cmp/jle
, sete
і у вас немає або запасного реєстру, або ви хочете взагалі не xor
вийти з незайнятого кодового шляху.
Немає визнаних ідіом, що занулюють нуль, які не впливають на прапори, тому найкращий вибір залежить від цільової мікроархітектури. У Core2, вставлення об'єднуючого в цілому може спричинити затримку 2 або 3 циклу. Здається, це дешевше на SnB, але я не витрачав багато часу, намагаючись виміряти. Використання mov reg, 0
/ setcc
загрожувало б значною мірою штрафу для старих процесорів Intel, а ще новіших процесорів Intel буде ще гірше.
Використання setcc
/ movzx r32, r8
є, мабуть, найкращою альтернативою для сімейства Intel P6 & SnB, якщо ви не можете набрати xor-zero перед інструкцією з встановлення прапора. Це має бути краще, ніж повторення тесту після нульового нуля. (Навіть не розглядайте sahf
/ lahf
або pushf
/ popf
). IvB може усунути movzx r32, r8
(тобто обробляти його за допомогою перейменування регістру без блоку виконання або затримки, наприклад, xor-zeroing). Хасвелл і пізніше лише виключають звичайні mov
інструкції, тому movzx
приймає блок виконання і має нульову затримку, що робить тест / setcc
/ movzx
гірше, ніж xor
/ тест / setcc
, але все одно принаймні настільки ж хороший, як тест / mov r,0
/ setcc
(і набагато краще на старих процесорах).
Використання setcc
/ movzx
без занулення спочатку погано для AMD / P4 / Silvermont, оскільки вони не відстежують депіляції окремо для підреєстрів. Невірно відображатиметься старе значення регістра. Використання mov reg, 0
/ setcc
для занулення / розриву залежності - це, мабуть, найкраща альтернатива, коли xor
/ тест / setcc
не є варіантом.
Звичайно, якщо вам не потрібно setcc
, щоб вихід був ширшим за 8 біт, вам нічого не потрібно нулювати. Однак остерігайтеся помилкових залежностей від процесорів, відмінних від P6 / SnB, якщо ви вибираєте реєстр, який нещодавно був частиною довгої ланцюга залежностей. (І будьте обережні, щоб викликати частковий загін реґулу чи додатковий загальний виклик, якщо ви викликаєте функцію, яка може зберегти / відновити реєстр, яким ви користуєтесь.)
and
з негайним нулем не є спеціальним випадком, незалежним від старого значення на будь-яких процесорах, про які я знаю, тому він не розриває ланцюги залежностей. Він не має переваг перед xor
багатьма недоліками.
Це корисно лише для написання мікропоказів, коли ви хочете залежність як частина тесту затримки, але хочете створити відоме значення шляхом нулювання та додавання.
Див. Http://agner.org/optimize/ для деталей мікроарха , включаючи, які нульові ідіоми визнаються порушеннями залежності (наприклад, sub same,same
є на деяких, але не на всіх процесорах, а xor same,same
розпізнається на всіх.) mov
Розриває ланцюг залежності від старого значення регістра (незалежно від значення джерела, нуля чи ні, тому що це mov
працює). xor
розбиває ланцюги залежностей лише у спеціальному випадку, коли src та dest - це той самий реєстр, через що mov
залишаються поза списком спеціально визнаних вимикачів залежностей. (Крім того, тому, що це не визнається ідіомою нуля, з іншими перевагами, які несе в собі.)
Цікаво, що найдавніша конструкція P6 (PPro через Pentium III) не визнавала xor
нульову функцію вимикача залежності, а лише як ідентифікацію нуля з метою уникнення часткових реєстрів , тому в деяких випадках варто було використовувати і те, mov
і іншеxor
-zeroing в такому порядку, щоб перервати dep, а потім знову нуль + встановити біт внутрішнього тегу, щоб високі біти дорівнювали нулю, так EAX = AX = AL.
Див. Приклад 6.17 Агнера Туману. у своєму мікроархіві pdf. Він каже, що це стосується також P2, P3 і навіть (ранньої?) PM. У коментарі до пов’язаної публікації в блозі йдеться про те, що цей контроль було лише PPro, але я тестував на Katmai PIII і @Fanael на Pentium M, і ми обидва виявили, що це не порушило залежності від затримки. -пов'язаний imul
ланцюг. Це, на жаль, підтверджує результати Agner Fog.
Якщо це дійсно робить ваш код приємнішим або зберігає інструкції, тоді обов'язково нуль, mov
щоб не торкатися прапорів, доки ви не введете проблеми з продуктивністю, крім розміру коду. Уникнення клопотуючих прапорів є єдиною розумною причиною не використовувати xor
, але іноді ви можете набрати нуль перед тим, що встановлює прапори, якщо у вас є запасний реєстр.
mov
-zero попереду setcc
краще затримки, ніж movzx reg32, reg8
після (за винятком Intel, коли можна вибрати різні регістри), але гірший розмір коду.
mov reg, src
також розриває ланцюги dep для OO-процесорів (незалежно від того, що src є imm32 [mem]
, або іншим реєстром). Цей розрив залежності не згадується в посібниках з оптимізації, оскільки це не особливий випадок, який відбувається лише тоді, коли src і dest є одним і тим же реєстром. Це завжди буває за інструкціями, які не залежать від їхньої мети. (за винятком того, що Intel реалізує popcnt/lzcnt/tzcnt
помилкове враження про призначення.)
mov
безкоштовним, лише нульовою затримкою. Частина "не приймає порт виконання" зазвичай не важлива. Пропускна здатність з плавним доменом легко може бути вузьким місцем, особливо з вантажами або магазинами в суміші.
xor r64, r64
що не просто марнувати байт. Як ви кажете xor r32, r32
, найкращий вибір, особливо з KNL. Дивіться розділ 15.7 "Особливі випадки незалежності" в цьому посібнику з мікрорахів, якщо ви хочете прочитати більше.