web-dev-qa-db-ja.com

レジスタの1の数をカウントする最速の方法ARMアセンブリ

それで、ビット操作に関して前にインタビューの質問がありました。同社は有名なGPU企業です。私はアセンブリ言語の背景がほとんどなく(コンピュータアーキテクチャの博士号取得者であるにもかかわらず奇妙です)、この物語が示すように、私はそれを失敗しました。質問は簡単でした:

「32ビットレジスタの1の数を数える高速コードを記述します。」

現在、私は腕の組み立てを研究している最中です。当然、私は再びこの問題を再検討し、ISAを研究するだけでこのコードを思いつきました。

エキスパートを武装させるために、これは正しいですか?これを行うためのより速い方法はありますか?初心者であるため、当然これは不完全だと思います。 "xx"のAND命令は冗長に感じられますが、ARM isa ...でレジスタをシフトする他の方法はありません。

R1は最後にビット数を含み、R2はカウントしたいビットを含むレジスタです。 r6は単なるダミーレジスタです。コメントは()で囲みます

    MOV   R1, #0                (initialize R1 and R6 to zero)
    MOV   R6, #0        
xx: AND   R6, R6, R2, LSR #1    (Right shift by 1, right most bit is in carry flag)
    ADDCS R1, #1                (Add #1 to R1 if carry  flag is set)
    CMP R2, #0                  (update the status flags if R2 == 0 or not)
    BEQ xx                      (branch back to xx until R2==0)
15
dean

事前に計算されたルックアップテーブルを使用して、反復回数を2または4に減らすことができます。

対数アプローチを使用することもできます。

詳細は このWikipediaの記事 を参照してください。

5
Alexey Frunze

このコードが高速かどうかは、プロセッサに依存します。確かに、Cortex-A8では非常に高速ではありませんが、Cortex-A9以降のCPUでは非常に高速で実行される可能性があります。

ただし、これは非常に短い解決策です。

R0に入力を期待し、r0に出力を返します

  vmov.32 d0[0], r0
  vcnt.8  d0, d0
  vmov.32 r0, d0[0]

  add r0, r0, r0, lsr #16
  add r0, r0, r0, lsr #8
  and r0, r0, #31

主な作業は vcnt.8命令 で行われ、NEONレジスタの各バイトのビットをカウントし、ビットカウントをD0のバイトに格納します。

vcnt.32形式はなく、.8のみなので、4バイトを水平方向に加算する必要があります。これが、残りのコードで行っていることです。

12

ビットハックの最良のリファレンスは

Bit Twiddling Hacksページには、

The best method for counting bits in a 32-bit
integer v is the following:

v = v - ((v >> 1) & 0x55555555);                    // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count

次に、gccおよびobjdump(または この素晴らしいオンラインgccツール )を使用して、この高レベルのスニペットがarm命令としてどのように見えるかを確認することをお勧めします。

00000000 <popcount>:
 0: 1043        asrs    r3, r0, #1
 2: f003 3355   and.w   r3, r3, #1431655765 ; 0x55555555
 6: 1ac0        subs    r0, r0, r3
 8: 1083        asrs    r3, r0, #2
 a: f000 3033   and.w   r0, r0, #858993459  ; 0x33333333
 e: f003 3333   and.w   r3, r3, #858993459  ; 0x33333333
12: 18c0        adds    r0, r0, r3
14: eb00 1010   add.w   r0, r0, r0, lsr #4
18: f000 300f   and.w   r0, r0, #252645135  ; 0xf0f0f0f
1c: eb00 2000   add.w   r0, r0, r0, lsl #8
20: eb00 4000   add.w   r0, r0, r0, lsl #16
24: 1600        asrs    r0, r0, #24
26: 4770        bx  lr

したがって、これは12命令をもたらすように見えます。これは、ほぼ同じ量のサイクルに変換できます。

上記の整数調整と libgcc で使用されるlook up tableアプローチを比較すると、追加のメモリアクセスを考慮すると、ルックアップテーブルはさらに遅くなるはずです。

00000028 <__popcountSI2>:
28: b410        Push    {r4}
2a: 2200        movs    r2, #0
2c: 4c06        ldr r4, [pc, #24]   ; (48 <__popcountSI2+0x20>)
2e: 4613        mov r3, r2
30: fa40 f103   asr.w   r1, r0, r3
34: 3308        adds    r3, #8
36: 2b20        cmp r3, #32
38: b2c9        uxtb    r1, r1
3a: 5c61        ldrb    r1, [r4, r1]
3c: 440a        add r2, r1
3e: d1f7        bne.n   30 <__popcountSI2+0x8>
40: 4610        mov r0, r2
42: bc10        pop {r4}
44: 4770        bx  lr
46: bf00        nop
48: 00000000    andeq   r0, r0, r0
<.. snipped ..>
6
auselen

これはARMとタグ付けされているため、 clz 命令が最も役立ちます。この問題は、母集団数としても記述されます。 gccには __ builtin_popcount() があります。 ARMツール と同様です。 このリンク (あなたの解決策を気にしないでください、ほとんど同じものでウェブページを作成した人もいます)、そして Dave Seal's バージョンがあり、6つの指示がありますclz以外のARM。 clz有利 であり、入力に応じて、より高速なアルゴリズムを生成するために使用できます。

auselen's と同様に、優れた読書提案、ハッカーの喜び this bit twiddling blog おそらく、グラフィックコンテキストでそのようなことについて話すのに役立ちます。少なくとも、Qtのブリットコードのいくつかを理解することは役に立ちました。ただし、population countルーチンをコーディングするのに役立ちます。

_carry add_ユニットは、分割統治の意味で役立ち、問題をO(ln n)にします。 clzは、データにonesまたはzeros

Hacker's Delight エントリには、Dave SealのARMコードに関するより多くの背景があります。

5
artless noise

long count_bits_long(ロング);

    vmov.32 d0[0], r0       // R0 --> SIMD

    vcnt.8  d0, d0          // count bits in bytes
    vpaddl.u8 d0, d0        // add adjacent pairs of bytes and put into 16b words
    vpaddl.u16 d0, d0       // add adjacent pairs of 16b words and put into 32b Word

    vmov.32 r0, d0[0]       // SIMD --> R0

    mov pc, lr              // return
2
rick-rick-rick
    LDR r0, = 0x000000FF;
    MOV r1, #0;
    MOV r3, #0; this will always be zero
    MOV r2,r0;
rep MOVS r2, r2, LSR #1;
    ADC r1,r1, r3;  this adds r1 with zero plus the carry bit
    CMP r2, #0;
    BNE rep

これで十分です。r3は、ADCを適切に機能させるための0のダミーレジスタです。

0
Avsharian