Чому в сучасних процесорах не існує інструкції `nand`?


52

Чому дизайнери x86 (або також інші архітектури процесора) вирішили не включати його? Це логічний воріт, який можна використовувати для побудови інших логічних воріт, таким чином, це швидко, як одна інструкція. Замість того, щоб ланцюжок notта andінструкції (обидва створені з nand), чому немає nandінструкції ?.


20
Яку корисну скриньку ви маєте для інструкції з Nand? Можливо, дизайнери x86 ніколи не знайшли жодного
PlasmaHH

16
ARM має BICінструкцію, яка є a & ~b. Великий палець-2 має ORNінструкцію, яка є ~(a | b). ARM досить сучасна. Кодування інструкції в наборі інструкцій CPU має свої витрати. Тож лише найкорисніші з них пробиваються в ISA.
Євген Ш.

24
@Amumu У нас ~(((a << 1) | (b >> 1)) | 0x55555555)теж може бути інструкція. Мета полягала б у тому, щоб її ~(((a << 1) | (b >> 1)) | 0x55555555)можна було перекласти в одну інструкцію замість 6. Отже, чому б ні?
користувач253751

11
@Amumu: Це не корисна справа, а також її ~ ні !. Важлива причина, чому ця інструкція є корисною, і де її можна застосувати. Ваші міркування схожі на те, що "Інструкція повинна бути там, щоб її можна було використовувати", але питання "для чого це використовувати настільки важливо, щоб корисно витрачати ресурси".
ПлазмаHH

4
Я програмував 45 років, написав кілька компіляторів і використовував деякі wierd логічні оператори, коли такі були доступні, такі як IMP, але я ніколи не використовував оператора NAND або інструкцію.
користувач207421

Відповіді:


62

http://www.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.alangref/idalangref_nand_nd_instrs.htm : POWER має NAND.

Але, як правило, сучасні процесори створені для того, щоб відповідати автоматизованому генерації коду компіляторами, і бітовий NAND дуже рідко вимагається. Побітові І і АБО частіше використовуються для маніпулювання бітовими полями в структурах даних. Насправді, SSE має AND-NOT, але не NAND.

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


5
@supercat AND-NOT зазвичай використовується для вимкнення бітів у бітно-набірній змінній. напр.if(windowType & ~WINDOW_RESIZABLE) { ... do stuff for variable-sized windows ... }
адиб

2
@adib: Так. Цікавою особливістю "і-не" є те, що на відміну від оператора "бітовий не" [~] розмір результату не має значення. Якщо fooце uint64_t, оператор foo &= ~something;може іноді очищати більше бітів, ніж було призначено, але якщо був &~=оператор, таких проблем можна уникнути.
supercat

6
@adib якщо WINDOW_RESIZABLEконстанта, то оптимізатор повинен оцінювати ~WINDOW_RESIZABLEпід час компіляції, тож це просто АБ під час виконання.
алефжеро

4
@MarkRansom: Ні, причинно-наслідковий результат повністю правильний з історії обчислень. Це явище проектування процесорів, оптимізованих для компіляторів, а не програмістів для збирання людей, було частиною руху RISC (хоча сам рух RISC є ширшим, ніж просто цей аспект). Процесори, призначені для компіляторів, включають ARM та Atmel AVR. В кінці 90-х та на початку 00-х років люди найняли авторів-компіляторів та програмістів ОС для розробки наборів інструкцій для процесора
slebetman

3
У ці дні операції реєстрації до реєстрації фактично безкоштовні порівняно з доступом до оперативної пам'яті. Впровадження зайвих інструкцій коштує кремнієву нерухомість у процесорі. Тому зазвичай буде лише одна форма побітових - АБО і побітових - І тому, що додавання операцій реєстру в регістрі порозрядного доповнення навряд чи будь-що сповільнить.
nigel222

31

Вартість таких функцій ALU становить

1) логіка, яка виконує саму функцію

2) селектор, який вибирає цю функцію, замість інших з усіх функцій ALU

3) вартість наявності цієї опції в наборі інструкцій (і відсутність іншої корисної функції)

Я згоден з вами, що 1) вартість дуже мала. Однак вартість 2) і 3) майже не залежить від функції. Я думаю, що в цьому випадку 3) вартість (біти, зайняті в інструкції) були причиною відсутності цієї конкретної інструкції. Біти в інструкції є дуже дефіцитним ресурсом для дизайнера процесора / архітектури.


29

Поверніть його - спочатку подивіться, чому Нанд був популярний в технічному дизайні апаратних засобів - він має кілька корисних властивостей. Потім запитайте, чи застосовуються ці властивості в інструкції з процесора ...

TL / DR - вони не роблять, тому немає жодних недоліків замість того, щоб використовувати And, Або чи Ні.

Найбільшою перевагою для вбудованої логіки Nand була швидкість, здобута зменшенням кількості логічних рівнів (транзисторних ступенів) між входами та виходами схеми. У процесорі тактова частота визначається швидкістю набагато складніших операцій, таких як додавання, тому прискорення операції AND не дозволить вам збільшити тактову частоту.

І кількість разів, коли вам потрібно поєднувати інші вказівки, зникає на малому рівні - достатньо, щоб Nand дійсно не заробляв своє місце в наборі інструкцій.


1
У випадках, коли ізоляція введення не потрібна, "і ні" в апараті буде здаватися дуже дешевим. Ще в 1977 році я сконструював контролер повороту для причепа мого батька, використовуючи два транзистори та два діоди на світло для виконання функції "XOR" [ліва лампа == xor (лівий сигнал, гальмо); права лампа == xor (правильний сигнал, гальмо)], по суті проводячи або виконуючи дві і-не функції для кожного світла. Я не бачив таких хитрощів, які використовуються в LSI-дизайні, але я думаю, що в TTL або NMOS, у випадках, коли будь-яка подача входу мала б належну здатність диска, такі трюки можуть врятувати схему.
supercat

12

Я хотів би погодитися з Брайаном тут, і Wouter і pjc50.

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

Подумайте про X86: AND(що є операцією "і"), ймовірно, дуже швидко. Те саме стосується NOT. Розглянемо трохи розбирання:

Вхідний код:

#include <immintrin.h>
#include <stdint.h>

__m512i nand512(__m512i a, __m512i b){return ~(a&b);}
__m256i nand256(__m256i a, __m256i b){return ~(a&b);}
__m128i nand128(__m128i a, __m128i b){return ~(a&b);}
uint64_t nand64(uint64_t a, uint64_t b){return ~(a&b);}
uint32_t nand32(uint32_t a, uint32_t b){return ~(a&b);}
uint16_t nand16(uint16_t a, uint16_t b){return ~(a&b);}
uint8_t nand8(uint8_t a, uint8_t b){return ~(a&b);}

Команда для складання:

gcc -O3 -c -S  -mavx512f test.c

Вихідна збірка (скорочена):

    .file   "test.c"
nand512:
.LFB4591:
    .cfi_startproc
    vpandq  %zmm1, %zmm0, %zmm0
    vpternlogd  $0xFF, %zmm1, %zmm1, %zmm1
    vpxorq  %zmm1, %zmm0, %zmm0
    ret
    .cfi_endproc
nand256:
.LFB4592:
    .cfi_startproc
    vpand   %ymm1, %ymm0, %ymm0
    vpcmpeqd    %ymm1, %ymm1, %ymm1
    vpxor   %ymm1, %ymm0, %ymm0
    ret
    .cfi_endproc
nand128:
.LFB4593:
    .cfi_startproc
    vpand   %xmm1, %xmm0, %xmm0
    vpcmpeqd    %xmm1, %xmm1, %xmm1
    vpxor   %xmm1, %xmm0, %xmm0
    ret
    .cfi_endproc
nand64:
.LFB4594:
    .cfi_startproc
    movq    %rdi, %rax
    andq    %rsi, %rax
    notq    %rax
    ret
    .cfi_endproc
nand32:
.LFB4595:
    .cfi_startproc
    movl    %edi, %eax
    andl    %esi, %eax
    notl    %eax
    ret
    .cfi_endproc
nand16:
.LFB4596:
    .cfi_startproc
    andl    %esi, %edi
    movl    %edi, %eax
    notl    %eax
    ret
    .cfi_endproc
nand8:
.LFB4597:
    .cfi_startproc
    andl    %esi, %edi
    movl    %edi, %eax
    notl    %eax
    ret
    .cfi_endproc

Як бачимо, для типів даних суб-64 розміру речі просто обробляються як довгі (отже, і l, а не l ), оскільки це "рідна" пропускна здатність мого компілятора, як здається.

Той факт, що movміж ними є s, пов'язаний лише з тим, що eaxце регістр, який містить зворотне значення функції. Зазвичай ви просто розраховуєтесь у ediреєстрі загальних цілей, щоб обчислити з результатом.

Для 64 біт це те саме - просто з "quad" (отже, кінцевими q) словами та rax/ rsiзамість eax/ edi.

Схоже, що для 128-бітових операндів і більше Intel не переймався реалізацією операції "не"; натомість компілятор виробляє всереєстр 1(самостійне порівняння регістра з самим собою, результат, збережений у регістрі з vdcmpeqdінструкцією), і xors це.

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


10

По-перше, не плутайте побіжно та логічні операції.

Бітові операції зазвичай використовуються для встановлення / очищення / перемикання / перевірки бітів у бітових полях. Жодна з цих операцій не потребує nand ("і не", також відомий як "біт ясно" є кориснішим).

Логічні операції в більшості сучасних мов програмування оцінюються за допомогою логіки короткого замикання. Тому зазвичай потрібен галузевий підхід до їх реалізації. Навіть коли компілятор може визначити, що коротке замикання та повна оцінка не має значення для поведінки програми, операнди для логічних операцій, як правило, не знаходяться у зручній формі для реалізації виразу, використовуючи операції побітової ASM.


10

NAND часто не реалізується безпосередньо, оскільки наявність інструкції AND неявно дає вам можливість стрибати за умовою NAND.

Виконання логічної операції в процесорі часто встановлює біти в регістрі прапорців.

Більшість реєстрів прапорів мають прапор ZERO. Нульовий прапор встановлюється, якщо результат логічної операції дорівнює нулю, і очищається в іншому випадку.

У більшості сучасних процесорів є інструкція про стрибок, яка стрибає, якщо встановлено нульовий прапор. Вони також мають тиск, який стрибає, якщо нульовий прапор не встановлений.

І і NAND є доповненнями. Якщо результат операції AND дорівнює нулю, то результат операції NAND дорівнює 1, і навпаки.

Отже, якщо ви хочете відпустити стрибок, якщо значення NAND двох значень істинне, просто виконайте операцію І та стрибайте, якщо встановлено прапор нуля.

Отже, якщо ви хочете відпустити стрибок, якщо NAND двох значень помилковий, просто виконайте операцію AND і стрибайте, якщо нульовий прапор ясний.


Дійсно - вибір умовної інструкції про стрибок дає вам вибір логіки, що перевертається та не перевертається, для цілого класу операцій, не потребуючи цього вибору для кожного окремо.
Кріс Страттон

Це мало бути найкращою відповіддю. Операції з нульовим прапором роблять NAND зайвим для логічних операцій, оскільки AND + JNZ і AND + JZ по суті є короткозамкненими / логічними AND і NAND відповідно, обидва приймають однакову кількість коду.
Лежати Райан

4

Тільки тому, що щось дешеве , не означає, що це рентабельно .

Якщо ми візьмемо вашу аргументацію ad absurdum, ми дійшли б висновку, що центральний процесор повинен складатися здебільшого з сотень ароматів інструкцій щодо NOP - адже вони найдешевші для реалізації.

Або порівняйте його з фінансовими інструментами: ви купуєте облігацію за 1 дол. США з 0,01% дохідністю лише тому, що можете? Ні, ви краще заощадите ці долари, поки у вас не вистачить придбати 10-доларову облігацію з кращою віддачею. Те ж саме стосується і силіконового бюджету на процесорі: він ефективний за допомогою багатьох дешевих, але марних операційних систем, таких як NAND, і вкладених збережених транзисторів у щось більш дороге, але справді корисне.

Не існує гонки, щоб мати якомога більше опенів. Як RISC проти CISC довели те, що знав Тьюрінг з самого початку: менше - більше. Насправді краще мати якомога менше операцій.


nopне може реалізувати всі інші логічні ворота, але nandабо norможе, ефективно відтворити будь-яку інструкцію, реалізовану в ЦП в програмному забезпеченні. Якщо ми скористаємося підходом RISC, тобто ..
Амуму

@Amumu Я думаю, ви змішуєте gateі instruction. Ворота використовуються для виконання інструкцій, а не навпаки. NOPце інструкція, а не ворота. І так, процесори містять тисячі або, можливо, навіть мільйони воріт NAND для виконання всіх інструкцій. Тільки не інструкція "NAND".
Agent_L

2
@Amumu Це не підхід RISC :) Це підхід "використовувати найширші абстракції", який не надто корисний поза дуже конкретних застосувань. Звичайно, nandце одна брама, яку можна використовувати для реалізації інших воріт; але ви вже маєте всі інші вказівки . Повторне їх виконання за допомогою nandінструкції було б повільніше . І вони використовуються занадто часто, щоб потерпіти, що на відміну від конкретного прикладу, який ви вибрали вишні, де nandможна створити коротший код (не швидший код, просто коротший); але це вкрай рідко, і користь просто не вартує витрат.
Луаан

@Amumu Якби ми використовували ваш підхід, ми не мали б позиційних номерів. Який сенс, коли ви можете просто сказати ((((()))))замість 5, правда? П'ять - це лише одне конкретне число, це занадто обмежує - набори набагато більш загальні: P
Луаан

@Agent_L Так, я знаю, що вказівки щодо реалізації воріт. nandреалізує всі ворота, тому неявно nandможе реалізувати всі інші вказівки. Потім, якщо програміст має nandінструкцію, він може вигадати свої власні інструкції, коли мислить у логічних воротах. Що я мав на увазі з самого початку, це те, що якщо він є настільки принциповим, чому йому не дали власну інструкцію (тобто опкод в логіці декодера), тому програміст може використовувати таку інструкцію. Звичайно після того, як я отримав відповідь, тепер я знаю, що це залежить від використання програмного забезпечення.
Амуму

3

На апаратному рівні, або на, або ні, це елементарна логічна операція. Залежно від технології (або залежно від того, що ви довільно називаєте 1 і те, що ви називаєте 0), або nand або ні може бути реалізовано дуже простим, елементарним способом.

Якщо ми ігноруємо випадок "ні", вся інша логіка будується з nand. Але не тому, що є деякі докази інформатики, що всі логічні операції можна побудувати з і - причина полягає в тому, що просто не існує елементарного методу побудови xor, або, і т. Д., Який краще, ніж побудувати його з nand's.

З комп'ютерними інструкціями ситуація інша. Інструкція nand може бути реалізована, і це було б трохи дешевше, ніж реалізація xor, наприклад. Але лише крихітний шматочок, тому що логіка, яка обчислює результат, крихітна порівняно з логікою, яка розшифровує інструкцію, переміщує операнди навколо, гарантує, що одна операція обчислюється, і підбирає результат і доставляє його в потрібне місце. Кожна інструкція виконує один цикл виконання, такий же як додавання, яке в десять разів складніше з точки зору логіки. Економія nand порівняно з xor була б незначною.

Тоді важливо, скільки інструкцій потрібно для операцій , які фактично виконуються за типовим кодом . Nand ніде не знаходиться у верхній частині списку часто запитуваних операцій. Набагато частіше зустрічаються запити та, або, не вимагаються. Дизайнери наборів процесорів та інструкцій вивчать безліч існуючих кодів та визначать, як різні інструкції впливатимуть на цей код. Вони, швидше за все, виявили, що додавання інструкції nand призведе до дуже незначного скорочення кількості інструкцій процесора, які виконують типовий код, а заміна якоїсь існуючої інструкції nand збільшить кількість виконуваних інструкцій.


2

Тільки тому, що NAND (або NOR) може реалізовувати всі ворота в комбінаційній логіці, не перекладається на ефективного побітового оператора таким же чином. Щоб реалізувати AND з використанням лише операцій NAND, де c = a AND b, вам доведеться мати c = a NAND b, тоді b = -1, тоді c = c NAND b (для NOT). Основними логічними операціями по логіці є AND, АБО, EOR, NOT, NAND і NEOR. Це не багато для висвітлення, і перші чотири, як правило, побудовані в будь-якому випадку. У комбінаційній логіці основні логічні схеми обмежені лише кількістю доступних воріт, що цілком відрізняється грою з м'ячем. Кількість можливих взаємозв'язків у програмованому масиві затворів, який звучить як те, що ви насправді шукаєте, справді було б дуже великою кількістю. Деякі процесори дійсно мають вбудовані масиви воріт.


0

Ви не реалізуєте логічний затвор лише тому, що він має функціональну повноту, особливо якщо інші логічні ворота є наявними. Ви реалізуєте те, що зазвичай використовується компіляторами.

NAND, NOR і XNOR дуже рідко потрібні. Крім класичних побітових операторів AND, OR і XOR, практична корисність матиме лише ANDN ( ~a & b), який не є NAND ( ~(a & b)). Якщо такі є, ЦП повинен це реалізувати (і справді деякі ЦП реалізують ANDN).

Для пояснення практичної корисності ANDN уявіть, що у вас є бітова маска, яка використовує багато біт, але вас цікавлять лише деякі з них, які є наступними:

enum my_flags {
    IT_IS_FRIDAY = 1,
    ...
    IT_IS_WARM = 8,
    ...
    THE_SUN_SHINES = 64,
    ...
};

Зазвичай ви хочете перевірити, чи є ваші інтереси до бітмаски чи

  1. Вони всі налаштовані
  2. Принаймні один встановлений
  3. Принаймні один не встановлений
  4. Жоден не встановлений

Почнемо зі збору ваших цікавих місць:

#define BITS_OF_INTEREST (IT_IS_FRIDAY | IT_IS_WARM | THE_SUN_SHINES)

1. Встановлюються всі інтереси: побіжно ANDN + логічно НЕ

Скажімо, ви хочете знати, чи всі ваші інтереси встановлені. Ви можете бачити це як (my_bitmask & IT_IS_FRIDAY) && (my_bitmask & IT_IS_WARM) && (my_bitmask & THE_SUN_SHINES). Однак, як правило, ви впадете в це

unsigned int life_is_beautiful = !(~my_bitmask & BITS_OF_INTEREST);

2. Принаймні один біт інтересу встановлюється: порозрядне І

Тепер скажемо, що ви хочете дізнатися, чи встановлений хоча б один інтерес. Ви можете бачити це як (my_bitmask & IT_IS_FRIDAY) || (my_bitmask & IT_IS_WARM) || (my_bitmask & THE_SUN_SHINES). Однак, як правило, ви впадете в це

unsigned int life_is_not_bad = my_bitmask & BITS_OF_INTEREST;

3. Принаймні один інтерес не встановлюється: розрядне ANDN

Тепер скажімо, що ви хочете знати, якщо хоча б один інтерес не встановлений. Ви можете бачити це як !(my_bitmask & IT_IS_FRIDAY) || !(my_bitmask & IT_IS_WARM) || !(my_bitmask & THE_SUN_SHINES). Однак, як правило, ви впадете в це

unsigned int life_is_imperfect = ~my_bitmask & BITS_OF_INTEREST;

4. Ніякий інтерес не встановлюється: побіжно І + логічно НЕ

Тепер скажемо, що ви хочете знати, чи не встановлені всі інтереси . Ви можете бачити це як !(my_bitmask & IT_IS_FRIDAY) && !(my_bitmask & IT_IS_WARM) && !(my_bitmask & THE_SUN_SHINES). Однак, як правило, ви впадете в це

unsigned int life_is_horrible = !(my_bitmask & BITS_OF_INTEREST);

Це звичайні операції, що виконуються на бітовій масці, плюс класичні побітові АБО та XOR. Я думаю , однак , що мова (який не є центральним процесором ) повинен включати в себе побітовое NAND, NOR і оператори XNOR (символи яких були б ~&, ~|а ~^), незважаючи на рідко. Я б не включав оператора ANDN в мову, оскільки, оскільки це не комутативно ( a ANDN bне те саме, що b ANDN a) - краще писати ~a & bзамість a ANDN b, колишній чіткіше демонструє асиметрію операції.

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