Який найкращий спосіб встановити реєстр на нуль у складі x86: xor, mov або та?


119

Усі наступні вказівки роблять те саме: встановлюємо %eaxнуль. Який спосіб є оптимальним (вимагає найменшої кількості машинних циклів)?

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax

6
Ви можете прочитати цю статтю
Michael Petch

Відповіді:


222

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 -зеронг.


Що особливого в нульових ідіомах, таких як xor на різних уршах

Деякі процесори розпізнають sub same,sameяк нульову ідіому xor, але всі процесори, які розпізнають будь-які нульові ідіоми, розпізнаютьxor . Просто використовуйте xorтак, що вам не доведеться турбуватися про те, який процесор визнає, яка нульова ідіома.

xor(на відміну від ідентифікаційної нульової ідіоми mov reg, 0) має деякі очевидні та деякі тонкі переваги (підсумковий список, тоді я розгорну на ці):

  • менший розмір коду, ніж mov reg,0. (Усі процесори)
  • дозволяє уникнути часткової реєстрації штрафних санкцій для подальшого коду. (Сімейство Intel P6 та сімейство SnB).
  • не використовує блок виконання, економлячи енергію та звільняючи ресурси виконання. (Сімейство Intel SnB)
  • менший взагалі (немає негайних даних) залишає місце в загальній лінії кеш-пам'яті для інструкцій, що знаходяться поблизу, якщо потрібно. (Сімейство Intel SnB).
  • не використовує записи у файлі фізичного реєстру . (Принаймні, сімейство Intel SnB (і P4), можливо, AMD, оскільки вони використовують подібну конструкцію PRF замість того, щоб підтримувати стан реєстру в ROB, як мікроархітектури сімейства Intel P6.)

Менший розмір машинного коду (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. Навіть без цього xorIvB потребує об'єднання лише тоді, коли високі 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.


TL: DR:

Якщо це дійсно робить ваш код приємнішим або зберігає інструкції, тоді обов'язково нуль, movщоб не торкатися прапорів, доки ви не введете проблеми з продуктивністю, крім розміру коду. Уникнення клопотуючих прапорів є єдиною розумною причиною не використовувати xor, але іноді ви можете набрати нуль перед тим, що встановлює прапори, якщо у вас є запасний реєстр.

mov-zero попереду setccкраще затримки, ніж movzx reg32, reg8після (за винятком Intel, коли можна вибрати різні регістри), але гірший розмір коду.


7
Більшість арифметичних вказівок OP R, S змушені процесором, що не працює в порядку, чекати, коли вміст регістра R буде заповнений попередніми інструкціями з регістром R як цільовим; це залежність від даних. Ключовим моментом є те, що на мікросхемах Intel / AMD є спеціальне обладнання для усунення залежності очікування даних щодо регістра R при виникненні XOR R, R, а це не обов'язково робити для інших інструкцій щодо нульового реєстру. Це означає, що інструкція XOR може бути запланована на негайне виконання, і саме тому Intel / AMD рекомендують використовувати її.
Іра Бакстер

3
@IraBaxter: Так, і щоб уникнути будь-якої плутанини (бо я бачив цю помилку на SO), mov reg, srcтакож розриває ланцюги dep для OO-процесорів (незалежно від того, що src є imm32 [mem], або іншим реєстром). Цей розрив залежності не згадується в посібниках з оптимізації, оскільки це не особливий випадок, який відбувається лише тоді, коли src і dest є одним і тим же реєстром. Це завжди буває за інструкціями, які не залежать від їхньої мети. (за винятком того, що Intel реалізує popcnt/lzcnt/tzcntпомилкове враження про призначення.)
Пітер Кордес,

2
@Zboson: "Затримка" інструкції без залежностей має значення лише в тому випадку, якщо в трубопроводі була бульбашка. Це приємно для усунення mov-усунення, але для нульових інструкцій вигода з нульовим затримкою вступає в дію лише після чого, як неправильний прогноз гілки або I $ miss, де виконання чекає розшифрованих інструкцій, а не для того, щоб дані були готові. Але так, усунення Mov не робить movбезкоштовним, лише нульовою затримкою. Частина "не приймає порт виконання" зазвичай не важлива. Пропускна здатність з плавним доменом легко може бути вузьким місцем, особливо з вантажами або магазинами в суміші.
Пітер Кордес

2
За словами агента, KNL не визнає незалежність 64-розрядних регістрів. Так xor r64, r64що не просто марнувати байт. Як ви кажете xor r32, r32, найкращий вибір, особливо з KNL. Дивіться розділ 15.7 "Особливі випадки незалежності" в цьому посібнику з мікрорахів, якщо ви хочете прочитати більше.
Z boson

3
ах, де добрий старий MIPS, з його "нульовим регістром", коли це потрібно.
hayalci
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.