最近のCPUには「nand」命令がないのはなぜですか?


52

x86設計者(または他のCPUアーキテクチャ)がx86を含めないことにしたのはなぜですか?これは、他の論理ゲートの構築に使用できる論理ゲートであるため、単一の命令として高速です。連鎖notand指示(両方ともから作成されるnand)ではなく、なぜnand指示がないのですか?


20
nand命令のユースケースは何ですか?おそらくx86デザイナーは何も見つけられなかった
PlasmaHH

16
ARMにはBIC命令がありますa & ~b。Arm Thumb-2には、というORN命令があり~(a | b)ます。ARMはかなりモダンです。CPU命令セットの命令のエンコードにはコストがかかります。したがって、最も「有用な」ものだけがISAに移行しています。
ユージーンSh。

24
@Amumu ~(((a << 1) | (b >> 1)) | 0x55555555)指導を受けることもできます。目的は~(((a << 1) | (b >> 1)) | 0x55555555)、6ではなく単一の命令に変換できるようにすることです。
user253751

11
@Amumu:それはユースケースではなく、また〜ではありません!ユースケースは、その命令が有用であり、それを適用できる場所の説得力のある理由です。あなたの推論は「命令は使用できるようにそこにあるべきだ」というようなものですが、質問は「そのために何を使用するかが非常に重要であり、リソースを使うのに役立つ」ということです。
PlasmaHH

4
私は45年間プログラミングを行い、いくつかのコンパイラを作成し、IMPなどの利用可能な場合はいくつかの奇妙な論理演算子を使用しましたが、NAND演算子または命令を使用したことがありません。
user207421

回答:


62

http://www.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.alangref/idalangref_nand_nd_instrs.htm:POWERにはNANDがあります。

しかし、一般的に最新のCPUは、コンパイラーによる自動コード生成に適合するように構築されており、ビット単位のNANDが必要になることはほとんどありません。ビット単位のANDおよびORは、データ構造内のビットフィールドを操作するためにより頻繁に使用されます。実際、SSEにはAND-NOTがありますが、NANDはありません。

すべての命令はデコードロジックにコストがかかり、他の何かに使用できるオペコードを消費します。特にx86のような可変長エンコーディングでは、短いオペコードが不足する可能性があり、長いオペコードを使用する必要があるため、潜在的にすべてのコードが遅くなります。


5
@supercat AND-NOTは、ビットセット変数のビットをオフにするために一般的に使用されます。例if(windowType & ~WINDOW_RESIZABLE) { ... do stuff for variable-sized windows ... }
アディブ

2
@adib:うん。"and-not"の興味深い機能は、 "bitwise not"演算子[〜]とは異なり、結果のサイズは重要ではないということです。場合foouint64_tをされ、文はfoo &= ~something;時々意図したよりも多くのビットをクリアかもしれませんが、あった場合&~=オペレータこのような問題を避けることができました。
supercat

6
@adib if WINDOW_RESIZABLEが定数の場合、オプティマイザーは~WINDOW_RESIZABLEコンパイル時に評価する必要があるため、これは実行時の単なるANDです。
アレフゼロ

4
@MarkRansom:いいえ、原因と結果は計算履歴から完全に正しいです。人間のアセンブリプログラマではなくコンパイラ用に最適化されたCPUを設計するこの現象は、RISCの動きの一部でした(ただし、RISCの動き自体はその側面よりも広いです)。コンパイラ用に設計されたCPUには、ARMおよびAtmel AVRが含まれます。90年代後半と早期00sの中で人々は、設計、CPUの命令セットにコンパイラ作家やOSのプログラマーを雇っ
slebetman

3
最近のレジスタ間操作は、RAMアクセスと比較して本質的に無料です。冗長な命令を実装すると、CPUのシリコンの不動産が犠牲になります。したがって、通常はビット単位のORとビット単位のANDの1つの形式のみが存在します。これは、ビット単位の補数レジスタ間演算を追加しても速度が低下することはほとんどないためです。
nigel222

31

このようなALU機能のコストは

1)機能自体を実行するロジック

2)すべてのALU関数のうち、他の関数の代わりにこの関数の結果を選択するセレクター

3)このオプションを命令セットに含めるコスト(および他の有用な機能を持たない)

1)コストが非常に小さいことに同意します。ただし、2)および3)のコストは、機能にほとんど依存しません。この場合、3)コスト(命令で占有されるビット)がこの特定の命令を持たない理由だと思います。命令のビットは、CPU /アーキテクチャ設計者にとって非常に少ないリソースです。


29

それを好転させる -最初にNandがハードウェアロジックデザインで人気があった理由を見てみましょう -それにはいくつかの有用なプロパティがあります。次に、これらのプロパティがまだCPU命令に適用されるかどうかを尋ねます...

TL / DR-そうではないので、代わりにAnd、Or、またはNotを使用することにマイナス面はありません。

ハードワイヤードNandロジックの最大の利点は速度であり、回路の入力と出力の間のロジックレベル(トランジスタステージ)の数を減らすことで得られました。CPUでは、クロック速度は加算などのはるかに複雑な操作の速度によって決定されるため、AND演算を高速化してもクロックレートを上げることはできません。

そして、他の命令を組み合わせる必要がある回数は、ごくわずかです。これは、Nandがインストゥルメントセットのスペースを実際に獲得できないようにするのに十分です。


1
入力の分離が不要な場合、ハードウェアでは「ではなく」が非常に安価に思えます。1977年に、ライトごとに2つのトランジスタと2つのダイオードを使用して、親のトレーラー用の方向指示器コントローラーを設計し、「XOR」機能を実行しました[左ランプ== xor(左信号、ブレーキ)。right lamp == xor(right signal、brake)]、本質的に各ライトの2つのAND-NOT関数をワイヤーで接続します。私はLSI設計でそのようなトリックを使用していませんが、TTLまたはNMOSでは、入力に給電するものが適切な駆動能力を備えている場合、そのようなトリックは回路を節約できると思います。
-supercat

12

ここでブライアンとWouterとpjc50に同意したいと思います。

また、汎用プロセッサ、特にCISCプロセッサでは、命令のスループットがすべて同じではないことを付け加えたいと思います。複雑な操作は単純なサイクルよりも多くのサイクルを要する場合があります。

X86を検討してください:(ANDこれは "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サイズのデータ​​型の場合、物事はすべてlongとして処理されます(したがって、lではなくand lです)。

そのそこの事実movの間では唯一の事実によるものであるeax関数の戻り値を含むレジスタです。通常、あなただけで計算しますedi汎用レジスターで計算して、結果を計算します。

64ビットの場合も同じです。 "quad"(したがって、末尾のq)単語、およびrax/のrsi代わりにeax/を使用するだけediです。

128ビット以上のオペランドの場合、Intelは「not」操作の実装を気にしなかったようです。代わりに、コンパイラーは全1レジスター(レジスターとそれ自体の自己比較、結果はvdcmpeqd命令とレジスターに保管されます)xorを生成します。

要するに、複数の基本命令で複雑な操作を実装することで、必ずしも操作が遅くなるわけではありません。複数の命令を処理する1つの命令が高速でない場合、それだけの利点はありません。


10

まず、ビット単位の操作と論理的な操作を混同しないでください。

ビット単位の操作は通常、ビットフィールドのビットを設定/クリア/トグル/チェックするために使用されます。これらの操作では、nandは必要ありません(「ビットクリア」とも呼ばれる「and not」の方が便利です)。

ほとんどの最新のプログラミング言語の論理演算は、短絡論理を使用して評価されます。そのため、通常、それらを実装するためのブランチベースのアプローチが必要です。コンパイラーが短絡評価と完全評価の違いによりプログラムの動作に違いがないと判断できる場合でも、通常、論理演算のオペランドはビット単位のasm演算を使用して式を実装するのに便利な形式ではありません。


10

AND命令を使用すると、NAND条件にジャンプする機能が暗黙的に与えられるため、NANDは直接実装されないことがよくあります。

CPUで論理演算を実行すると、多くの場合、フラグレジスタにビットが設定されます。

ほとんどのフラグレジスタにはゼロフラグがあります。論理演算の結果がゼロの場合、ゼロフラグが設定され、それ以外の場合はクリアされます。

最新のCPUのほとんどには、ゼロフラグが設定されている場合にジャンプするジャンプ命令があります。また、ゼロフラグが設定されていない場合にジャンプする命令もあります。

ANDとNANDは補完です。AND演算の結果がゼロの場合、NAND演算の結果は1になり、逆も同様です。

したがって、2つの値のNANDが真である場合にジャンプする場合は、AND演算を実行し、ゼロフラグが設定されている場合はジャンプします。

したがって、2つの値のNANDがfalseの場合にジャンプする場合は、AND演算を実行し、ゼロフラグがクリアされている場合はジャンプします。


確かに-条件付きジャンプ命令の選択により、操作のクラス全体に対して反転ロジックと非反転ロジックの選択が可能になり、それぞれ個別にその選択を実装する必要がなくなります。
クリスストラットン

これが最良の答えだったはずです。AND + JNZおよびAND + JZは本質的にそれぞれ短絡/論理ANDおよびNANDであり、どちらも同じ数のオペコードを使用するため、ゼロフラグ操作は論理演算に対してNANDを不要にします。
ライライアン

4

何かが安いからといって、費用対効果が高いというわけではありません。

不合理な議論をすると、CPUはほとんど何百種類のNOP命令で構成する必要があるという結論に達します。これは、実装が最も安価だからです。

または、金融商品と比較してください。可能だからといって、0.01%のリターンで1ドルの債券を購入しますか。いいえ、より良いリターンで10ドルの債券を購入するのに十分になるまで、それらのドルを節約したいです。CPUのシリコンバジェットについても同じことが言えます。NANDのような安価だが役に立たない多くの操作を行い、保存されたトランジスタをもっと高価でありながら真に有用なものにすることが効果的です。

可能な限り多くのopを持つ競争はありません。RISC対CISCは、Turingが最初から知っていたことを証明したので、少ないほど優れています。実際には、できるだけ少ないopsを使用することをお勧めします。


nop他のすべての論理ゲートを実装することはできませんが、nandまたはnor、効果的にソフトウェアでCPUに実装されている任意の命令を再作成することができます。RISCアプローチを採用する場合、それは..
Amumu

@Amumu私はあなたがアップミキシングていると思うgateinstruction。ゲートは命令を実装するために使用されますが、その逆ではありません。NOP命令ではなく、ゲートです。そして、はい、CPUにはすべての命令を実装するために数千または多分数百万のNANDゲートが含まれています。「NAND」命令ではありません。
Agent_L

2
@Amumu RISCアプローチではありません:)これは「最も広い抽象化を使用する」アプローチです。これは、非常に特定のアプリケーション以外ではあまり有用ではありません。もちろん、nand他のゲートを実装するために使用できる1つのゲートです。しかし、あなたはすでに他のすべての指示を持ってますnand命令を使用してそれらを再実装すると、速度が低下します。また、nand短いコード(高速なコードではなく、短いコード)を生成するチェリーピッキングの特定の例とは異なり、それらを許容するにはあまりにも頻繁に使用されます。しかし、それは非常にまれであり、利益は単にコストに見合うものではありません。
ルアーン

@Amumuあなたのアプローチを使用した場合、位置番号はありません。((((()))))5の代わりに単に言うことができるポイントは何ですか?5は特定の1つの数字にすぎず、制限が大きすぎます-セットははるかに一般的です:P
Luaan

@Agent_Lはい、ゲートに命令が実装されていることは知っています。nandすべてのゲートを実装するため、暗黙的にnand他のすべての命令を実装できます。次に、プログラマがnand使用可能な命令を持っている場合、論理ゲートで考えるときに彼は彼自身の命令を発明することができます。私が最初から意味したのは、それが非常に基本的な場合、なぜ独自の命令(つまり、デコーダロジックのオペコード)が与えられなかったので、プログラマがそのような命令を使用できるということです。もちろん、私が答えを得た後、ソフトウェアの使用法に依存することがわかりました。
アムム

3

ハードウェアレベルでは、nandまたはnorは基本的な論理演算です。テクノロジーに応じて(または、1を任意に呼び出すものと0を呼び出すものに応じて)、nandまたはnorは、非常に単純な基本的な方法で実装できます。

「nor」の場合を無視すると、他のすべてのロジックはnandから構築されます。しかし、すべての論理演算を構築できるというコンピューターサイエンスの証拠があるからではありません-その理由は、norから構築するよりも優れているxorなどを構築するための基本的な方法がないからです。

コンピューターの指示については、状況は異なります。nand命令を実装できます。たとえば、xorを実装するよりも少し安くなります。ただし、結果を計算するロジックは、命令をデコードし、オペランドを移動し、1つの操作のみが計算されるようにし、結果を取得して適切な場所に配信するロジックに比べて、結果を計算するロジックはごくわずかです。各命令の実行には1サイクルかかります。これは、ロジックの点で10倍複雑な加算と同じです。nand vs. xorの節約はごくわずかです。

ここで重要なのは、通常のコードで実際に実行される操作に必要な命令の数です。Nandは、一般的に要求される操作のリストの最上位に近いところにありません。要求されること、または要求されないことははるかに一般的です。プロセッサおよび命令セットの設計者は、多くの既存のコードを調べ、異なる命令がそのコードにどのように影響するかを判断します。ほとんどの場合、nand命令を追加しても、通常のコードを実行するために実行されるプロセッサ命令の数はほとんど減少せず、既存の命令をnandに置き換えると実行される命令の数が増えることがわかりました。


2

NAND(またはNOR)がすべてのゲートを組み合わせロジックで実装できるからといって、同じように効率的なビット単位の演算子に変換されません。NAND演算(c = a AND b)のみを使用してANDを実装するには、c = a NAND b、次にb = -1、c = c NAND b(NOTの場合)が必要です。基本的な論理ビット演算は、AND、OR、EOR、NOT、NAND、およびNEORです。それをカバーすることはそれほど多くありません、そして、最初の4つは一般にとにかく組み込まれます。組み合わせ論理では、基本的な論理回路は利用可能なゲートの数によってのみ制限されます。これはまったく異なる球技です。プログラマブルゲートアレイ内で可能な相互接続の数は、実際に何を求めているかのように聞こえますが、実際には非常に多くなります。実際、一部のプロセッサにはゲートアレイが組み込まれています。


0

特に他のロジックゲートがネイティブで利用できる場合、機能的な完全性があるという理由だけで、ロジックゲートを実装しません。コンパイラが最も使用する傾向のあるものを実装します。

NAND、NOR、XNORはほとんど必要ありません。古典的なビット演算子AND、OR、およびXORに加えて、ANDN(~a & b)-NAND(~(a & b))ではない- のみが実用的です。ある場合、CPUはそれを実装する必要があります(実際、一部のCPUはANDNを実装します)。

ANDNの実用的な実用性を説明するために、多くのビットを使用するビットマスクがあると想像してください。

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

通常、ビットマスク内の関心のあるビットについてチェックするかどうか

  1. それらはすべて設定されています
  2. 少なくとも1つが設定されています
  3. 少なくとも1つが設定されていません
  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.対象の少なくとも1ビットが設定されます:ビット単位AND

ここで、少なくとも1ビットの関心が設定されているかどうかを知りたいとしましょう。としてそれを見ることができます(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.対象の少なくとも1ビットが設定されていません:ビット単位のANDN

ここで、少なくとも1ビットの関心が設定されていないかどうかを知りたいとしましょう。としてそれを見ることができます!(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.関心のあるビットは設定されていません:ビット単位のAND +論理否定

ここで、すべての対象ビットが設定されていないかどうかを知りたいとしましょう。としてそれを見ることができます!(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);

これらは、ビットマスクで実行される一般的な操作に加えて、古典的なビット単位のORおよびXORです。まれに使用されるにもかかわらず、言語CPUではない)にビット単位のNAND、NOR、およびXNOR演算子(シンボルは~&~|および~^)を含める必要があると思います。ただし、言語にはANDN演算子を含めません。これは可換でa ANDN bはない(とは異なります)の代わりb ANDN aに記述した方がよいため、前者は操作の非対称性をより明確に示しています。~a & ba ANDN b

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.