x86アセンブリで大きさの順序を計算する最速の方法


12

タスクは簡単です。可能な限り少ないクロックサイクルを使用して整数の大きさを計算するアセンブリを記述します。

  • 大きさの順序はと定義されてlog10log2ます。
  • 有効な入力の範囲はある0に、包括的。その範囲外の入力の動作は未定義です。1012
  • 値は、最も近い整数に切り捨てる必要が0あります0。ただし、入力を指定した場合、出力はである必要があります。(これは符号なし整数で可能な負の無限大の最良の表現であると考えることができます)。
  • x86アセンブリでなければなりません。
  • 整数は、静的/インライン整数ではなく、ランタイム値である必要があります。そのため、コンパイル時にそれが何であるかはわかりません。
  • 既にレジスタに整数がロードされていると仮定します。(ただし、明確にするために回答のレジスタに値を設定することも含めてください)。
  • 外部ライブラリまたは関数を呼び出すことはできません。
  • Intelのドキュメントに記載されている手順を自由に使用できます。
  • いいえC
  • 〜7個のIntel Coreアーキテクチャのいずれでも使用できます(10ページにリスト)。理想的にはNehalem(Intel Core i7)。

勝利の答えは、可能な限り少ないクロックサイクルを使用するものです。つまり、1秒あたりの操作数が最も多くなります。おおよそのクロックサイクルの概要は、http://www.agner.org/optimize/instruction_tables.pdfにあります。クロックサイクルの計算は、回答が投稿された後に発生する可能性があります。


「FYL2X」およびその他のFPU命令は許可されますか?
デジタル外傷

1
結果は整数でなければなりませんか?もしそうなら、それはどのように丸められるべきですか?
デジタル外傷

3
入力と出力は両方とも整数です、はい?ただし、入力は10 ^ 12にもなる可能性があるため、32ビット整数には大きすぎます。したがって、64ビット整数の入力を想定していますか?
ポールR

3
勝利基準は最大または平均サイクル数に基づいていますか?それが平均的な場合、入力の分布はどうですか?
Runer112

2
どのプロセッサが対象ですか?リンクされたドキュメントには、20を超えるさまざまなプロセス(AMD、Intel、その他)がリストされており、レイテンシにはかなりのばらつきがあります。
デジタル外傷

回答:


8

7サイクル、一定時間

このSO Questionへの私の答えに基づいたソリューションを次に示します。BSRを使用して、数値を保持するために必要なビット数をカウントします。これは、多くのビットが保持できる最大数を表すために必要な小数桁数を検索します。その後、実際の数がその桁数で最も近い10のべき乗より小さい場合、1を引きます。

    .intel_syntax noprefix
    .globl  main
main:
    mov rdi, 1000000000000              #;your value here
    bsr rax, rdi
    movzx   eax, BYTE PTR maxdigits[1+rax]
    cmp rdi, QWORD PTR powers[0+eax*8]
    sbb al, 0
    ret
maxdigits:
    .byte   0
    .byte   0
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .byte   1
    .byte   2
    .byte   2
    .byte   2
    .byte   3
    .byte   3
    .byte   3
    .byte   3
    .byte   4
    .byte   4
    .byte   4
    .byte   5
    .byte   5
    .byte   5
    .byte   6
    .byte   6
    .byte   6
    .byte   6
    .byte   7
    .byte   7
    .byte   7
    .byte   8
    .byte   8
    .byte   8
    .byte   9
    .byte   9
    .byte   9
    .byte   9
    .byte   10
    .byte   10
    .byte   10
    .byte   11
    .byte   11
    .byte   11
    .byte   12
powers:
    .quad   0
    .quad   10
    .quad   100
    .quad   1000
    .quad   10000
    .quad   100000
    .quad   1000000
    .quad   10000000
    .quad   100000000
    .quad   1000000000
    .quad   10000000000
    .quad   100000000000
    .quad   1000000000000

ubuntu用にGCC 4.6.3でコンパイルし、終了コードで値を返します。

最新のプロセッサのサイクルテーブルを解釈する自信はありませんが、Nehalimプロセッサで@DigitalTraumaの方法を使用すると、7が得られます。

ins        uOps Latency
---        -    - 
BSR r,r  : 1    3
MOVZX r,m: 1    -
CMP m,r/i: 1    1 
SBB r,r/i: 2    2

ああ-私は私のものを書き始めた後DigitalTraumaを見ました。同様のアイデア。彼のカウント方法を使用して、私がget 3,1,1,1 = 6 BSR、MOV、CMP、SBBのためだと思う
AShelly

うん、あなたのものは私のものを打ち負かすと思う。ただ、a)私はアセンブリプログラマーではなく、b)アセンブリは私たちだけの人間に残しておくのが最善です;-)
デジタルトラウマ

The integer must be a runtime value, not a static/inline integer. So we don't know what it is at compile time.

1
次の行には、「レジスタに既に整数がロードされていると想定しています(ただし、わかりやすくするために、回答のレジスタに値を設定することを含めてください)」。これは私がやったことです。
AShelly

movzx eaxをmov alに置き換えます。eaxの上位24ビットはすでにゼロであるため、zxは冗長です(そして高価です)。
ピーターフェリー

6

ベストケース8サイクル、ワーストケース12サイクル

質問では明確ではないので、これはIvy Bridgeの待ち時間に基づいています。

ここでのアプローチはbsr、貧乏人のlog2()として(ビットスキャンリバース)命令を使用することです。結果は、ビット0〜42のエントリを含むジャンプテーブルへのインデックスとして使用されます。64ビットデータに対する操作が暗黙的に必要であると仮定すると、bsr命令の使用はOKです。

最良の場合の入力では、ジャンプテーブルエントリはbsr結果を絶対値に直接マッピングできます。たとえば、範囲が32〜63の入力の場合、bsr結果は5になり、これは大きさ1にマッピングされます。この場合、命令パスは次のとおりです。

Instruction    Latency

bsrq                 3
jmp                  2
movl                 1
jmp                  2

total                8

最悪の場合、bsr結果は2つの可能な大きさにマッピングされるため、ジャンプテーブルエントリはcmp入力が10 nを超えるかどうかを確認するために1回追加します。たとえば、範囲64〜127の入力の場合、bsr結果は6になります。対応するジャンプテーブルエントリは、入力が100より大きいかどうかを確認し、それに応じて出力の大きさを設定します。

最悪の場合のパスに加えて、で使用する64ビットの即値をロードするmov命令が追加されているcmpため、最悪の場合の命令パスは次のようになります。

Instruction    Latency

bsrq                 3
jmp                  2
movabsq              1
cmpq                 1
ja                   2
movl                 1
jmp                  2

total               12

コードは次のとおりです。

    /* Input is loaded in %rdi */
    bsrq    %rdi, %rax
    jmp *jumptable(,%rax,8)
.m0:
    movl    $0, %ecx
    jmp .end
.m0_1:
    cmpq    $9, %rdi
    ja  .m1
    movl    $0, %ecx
    jmp .end
.m1:
    movl    $1, %ecx
    jmp .end
.m1_2:
    cmpq    $99, %rdi
    ja  .m2
    movl    $1, %ecx
    jmp .end
.m2:
    movl    $2, %ecx
    jmp .end
.m2_3:
    cmpq    $999, %rdi
    ja  .m3
    movl    $2, %ecx
    jmp .end
.m3:
    movl    $3, %ecx
    jmp .end
.m3_4:
    cmpq    $9999, %rdi
    ja  .m4
    movl    $3, %ecx
    jmp .end
.m4:
    movl    $4, %ecx
    jmp .end
.m4_5:
    cmpq    $99999, %rdi
    ja  .m5
    movl    $4, %ecx
    jmp .end
.m5:
    movl    $5, %ecx
    jmp .end
.m5_6:
    cmpq    $999999, %rdi
    ja  .m6
    movl    $5, %ecx
    jmp .end
.m6:
    movl    $6, %ecx
    jmp .end
.m6_7:
    cmpq    $9999999, %rdi
    ja  .m7
    movl    $6, %ecx
    jmp .end
.m7:
    movl    $7, %ecx
    jmp .end
.m7_8:
    cmpq    $99999999, %rdi
    ja  .m8
    movl    $7, %ecx
    jmp .end
.m8:
    movl    $8, %ecx
    jmp .end
.m8_9:
    cmpq    $999999999, %rdi
    ja  .m9
    movl    $8, %ecx
    jmp .end
.m9:
    movl    $9, %ecx
    jmp .end
.m9_10:
    movabsq $9999999999, %rax
    cmpq    %rax, %rdi
    ja  .m10
    movl    $9, %ecx
    jmp .end
.m10:
    movl    $10, %ecx
    jmp .end
.m10_11:
    movabsq $99999999999, %rax
    cmpq    %rax, %rdi
    ja  .m11
    movl    $10, %ecx
    jmp .end
.m11:
    movl    $11, %ecx
    jmp .end
.m11_12:
    movabsq $999999999999, %rax
    cmpq    %rax, %rdi
    ja  .m12
    movl    $11, %ecx
    jmp .end
.m12:
    movl    $12, %ecx
    jmp .end

jumptable:
    .quad   .m0
    .quad   .m0
    .quad   .m0
    .quad   .m0_1
    .quad   .m1
    .quad   .m1
    .quad   .m1_2
    .quad   .m2
    .quad   .m2
    .quad   .m2_3
    .quad   .m3
    .quad   .m3
    .quad   .m3
    .quad   .m3_4
    .quad   .m4
    .quad   .m4
    .quad   .m4_5
    .quad   .m5
    .quad   .m5
    .quad   .m5_6
    .quad   .m6
    .quad   .m6
    .quad   .m6
    .quad   .m6_7
    .quad   .m7
    .quad   .m7
    .quad   .m7_8
    .quad   .m8
    .quad   .m8
    .quad   .m8_9
    .quad   .m9
    .quad   .m9
    .quad   .m9
    .quad   .m9_10
    .quad   .m10
    .quad   .m10
    .quad   .m10_11
    .quad   .m11
    .quad   .m11
    .quad   .m11_12
    .quad   .m12
    .quad   .m12
    .quad   .m12

.end:
/* output is given in %ecx */

これは、私が書いた概念実証Cコードの gccアセンブラー出力からほとんど生成されました。Cコードでは、計算可能なgotoを使用してジャンプテーブルを実装しています。また__builtin_clzll()bsr命令にコンパイルされるgccビルトインも使用します(プラスxor)。


これに到達する前に、いくつかの解決策を検討しました。

  • FYL2X自然対数を計算するにはFMUL、必要な定数を使用します。[tag:instruction:golf]コンテストであれば、おそらくこれが勝つでしょう。ただしFYL2X、Ivyブリッジでは90〜106のレイテンシがあります。

  • ハードコードされたバイナリ検索。これは実際に競争力があるかもしれません-私はそれを実装するために他の誰かに任せます:)。

  • 結果の完全なルックアップテーブル。これは理論的には最速だと確信していますが、ムーアの法則が引き続き適用される場合は、おそらく数年のうちに1TBのルックアップテーブルが必要になります(まだ実用的ではありません)。


必要に応じて、許可されたすべての入力の平均レイテンシを計算できます。
デジタル外傷

jmpそして、jccだけコストスループット、遅延はありません。分岐予測+投機的実行は、制御依存関係がデータ依存関係チェーンの一部ではないことを意味します。
ピーター・コーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.