7

idivx86 アセンブリ命令EDX:EAXが特定のレジスタで除算 (64 ビット) するのに対し、乗算を含む他の数学演算は単一の入力レジスタと出力レジスタで単純に動作するのはなぜですか?

乗算:

mov eax, 3
imul eax, 5

分割:

mov edx, 0
mov eax, 15
mov ebx, 5
idiv ebx

が残りを格納するために使用されることは承知していますEDXが、なぜこの動作について別の指示がないのでしょうか? 私には矛盾しているように思えます。

4

4 に答える 4

9

命令セットは、任意幅の整数演算を効率的に実装するために必要な命令を提供します。加算と減算の場合、固定幅の結果を超える場合に知っておく必要があるのは、演算の結果がキャリー (加算の場合) であるかボロー (減算の場合) であるかだけです。これがキャリーフラグがある理由です。乗算では、2 つの単語を乗算して、ダブル ワードの結果を得ることができる必要があります。これが でimul結果を生成する理由edx:eaxです。割り算は、倍角数を割って商と余りを求められる必要があります。

これらの特定の操作が必要な理由を理解するには、Knuth のThe Art of Computer Programming, Volume 2 を参照してください。

なぜ x86 命令セットに乗算と除算の命令がこれほど多くないのかというと、2 の累乗でない乗算と除算は他の命令よりもはるかにまれであるため、Intel はおそらく使用したくなかったのでしょう。より頻繁に使用される命令に使用できるオペコードをアップします。汎用プログラムのほとんどの乗算と除算は、2 の累乗です。leaこれらには、代わりにビットシフトまたは命令を使用できます。

于 2012-09-25T15:52:39.023 に答える
5

「倍幅」の乗算もあります (単一オペランドmulまたはimul)。

「なぜ商だけを与える 2 つのオペランドがないのか」と尋ねている場合idiv、私にはよくわかりません (私には理論がありますが、Intel で働いているわけではありません)。 ..

2 の累乗ではない剰余を使って剰余乗算を実行したい場合にうまく機能します。a を実行してからmul直接 a を使用するdivと、すべてが適切な場所に配置されます。これは結果であり、理由ではありません。その理由についてはインテルに問い合わせる必要があります..しかし、ここに理論があります。8086 時代には、倍幅乗算しかありませんでした (ソフトウェアで行うのと同じように、早期終了を伴う低速の反復乗算でした)。後で、より柔軟な乗算がいくつか追加されましたが、除算ではそれは起こりませんでした。おそらく、それほど差し迫ったものではありませんでした。結局のところ、除算は比較的まれですが、構造体の配列にインデックスを付ける場合など、小さな定数による乗算が必要になることがよくあります。

于 2012-09-25T15:40:31.343 に答える
4

ここでは、1 つの質問に 2 つの質問があります。まず、倍幅の入力または出力の問題があります。結果の上位半分を含む、完全に拡大する乗算を行う 1 オペランドのMUL / IMULフォームを無視しています: N * N => 2N ビット、実行中 EDX:EAX = EAX * src. これが役立つ理由については、他の回答を参照してください。

BMI2では、3 つの明示的なオペランド (2 つの出力と 1 つの入力) と 1 つの暗黙的なオペランド (2 番目のソース = EDX) を持つ、より柔軟な完全乗算命令 MULX も導入されました


imul次に、 DIV/IDIV では使用できないもう 1 つの即値オペランドを使用する例を示します。

16 / 8 => 8 ではなく、8 ビット / imm8 => 8 ビットの商/剰余を実行する、実際には即値 div であるあいまいな命令が 1 つあります。これはAAMと呼ばれ、64 ビット モードでは使用できません。アセンブラーはデフォルトで 10 で除算しますが (BCD の意図したユースケースの場合)、imm8 と同じオペコードです。 DIV または AAM を使用して 0 ~ 99 の整数を 2 つの ASCII 数字に変換する方法を次に示します。また、AAM とDIV r/m8.

Intel はいつでも IDIV の即時バージョンを追加できたはずですが、実行しませんでした。私の推測では、DIV / IDIV は十分に遅い (そして十分にまれである) ため、余分なオーバーヘッドmov reg, imm32は無視できるほどであり、そのような命令にオペコード スペース (およびデコーダ トランジスタ) を費やすことは決して価値があるとは見なされませんでした。


さらに重要なことは、コンパイル時の定数による実際のハードウェア除算は、通常、パフォーマンスではなくコードサイズにのみ役立ちます。モジュラー乗法逆数は、90 年代から (コンパイラーのライターによって) よく知られています。コンパイラは定数による除算さえ使用していないため、Intel は、この手法が知られるようになった後に設計された CPU にそのための命令を追加する可能性はほとんどありませんでした。たとえば、clang は次のようにコンパイルunsigned int div10(unsigned int a) { return a/10; }されます。

    mov     ecx, edi         # just to zero-extend to 64-bit
    mov     eax, 3435973837  # a sign-extended imm32 can't represent this constant, I guess.  clang uses imul r,r,imm for other cases.
    imul    rax, rcx         # 64-bit multiply instead of 32x32 => 64 in two separate regs
    shr     rax, 35          # extract part of the high-half result.
    ret

符号付き除算にはさらにいくつかの命令が必要であり、単純ではない除数の結果をいじる追加/減算が必要になる場合があります。Godbolt でいくつかの例を参照してください。それでも、 Haswell で22 ~ 29 サイクルのレイテンシがDIV r64あり、スループットが悪いなど、非常に遅いハードウェア除算命令よりも高速です。


彼らがより多くの命令にオペコード(およびデコーダトランジスタ/電力)を費やすつもりなら、単一幅の被除数を持つ2レジスタ形式のIDIVがコンパイラに役立つかもしれません.

ハードウェア除算器が内部でどのように実装されているかについてはあまり知らないので、通常の 2N / N => N の代わりに N / N => N ビット除算のみを行うことで節約できる場合は IDK を使用します。コンパイラ出力では、ほとんどすべての分割は、CDQ または の後に行われxor edx,edxます。多くの x86 マイクロアーキテクチャでは、除算は可変レイテンシーであるため、被除数が実際には N ビットしかない場合にスピードアップが必要な場合は、ハードウェアが既にそれを探していると思われます。ただし、Skylake DIV/IDIV r32 は一定の 26c レイテンシーです(ただし、64 ビット除数ははるかに遅く、依然として非常に変動するレイテンシーがあります)。

おそらく、DIV r32, r32命令はまだ2つの出力(商と剰余)を生成するでしょう.2つの入力レジスタで推測しますか?そのため、入力を保存するために追加の MOV 命令が必要になることがよくあります。あるいは、商または余りを選択して 1 つの宛先に移動するか、商または剰余に 2 つの別々のオペコードを使用するのにすぐに時間がかかるでしょうか?

この時点で、3 つの明示的なオペランドを使用して、MULXのように機能する VEX コード バージョンを追加できます。ただし、MULX の使用目的は、拡張精度の乗算を拡張精度のキャリー付き加算とインターリーブできるようにすることであるため、DIVX r64(quotient), r64(remainder), r/m64(divisor)(RDX で暗黙の被除数がある場合) は大幅に異なります (拡張精度にはあまり有用ではありません)。彼らはおそらく、暗黙の被除数を RDX:RAX にするでしょう。あるいは、DIVX はすでにビデオ コーデック / 会社の商標であるため、DIVX とは呼ばないかもしれません。

于 2016-11-18T04:52:13.083 に答える
4

加算と減算の場合、オーバーフローはキャリー フラグによって処理される単一のビットです。任意の 2 つの N ビット オペランドを取り、それらを乗算する場合、結果を格納するために 2*N ビットが必要です。非常に簡単です。自分で 0xFF * 0xFF = 0xFE01 を試してみてください。N ビット サイズのレジスタのみを使用した場合、乗算命令は非常に制限されます。除算は、N ビットを取得する乗算除算 2*N ビットの反対です。N ビット * N ビット = 2*N ビット数を気にする場合は、2*N ビット数 / N ビット数 = N ビット数も実装する必要があります。残念ながら、ハードウェアは言語よりも多くのことを行いますが、言語もこれを認識して実行する必要があります.2バイトを乗算すると、結果変数が16ビットより小さい場合、コンパイラは精度について文句を言う必要があります。

于 2012-09-25T19:45:57.620 に答える