アセンブリを使用してこの8ビットの位置ポップカウントを最適化する方法は?

shenwei356:

この投稿は、_mm_add_epi32のGolangアセンブリ実装に関連しています。ここでは、ペアの要素を2つの[8]int32リストに追加し、更新された最初の要素を返します。

pprofプロファイルによると、渡すこと[8]int32は高価であることがわかったので、リストのポインターを渡す方がはるかに安価であると思います。これがgoバージョンです:

func __mm_add_epi32_inplace_purego(x, y *[8]int32) {
    (*x)[0] += (*y)[0]
    (*x)[1] += (*y)[1]
    (*x)[2] += (*y)[2]
    (*x)[3] += (*y)[3]
    (*x)[4] += (*y)[4]
    (*x)[5] += (*y)[5]
    (*x)[6] += (*y)[6]
    (*x)[7] += (*y)[7]
}

この関数は、2つのレベルのループで呼び出されます。

アルゴリズムは、バイトの配列での位置の母集団カウント計算します。

@fuzからのアドバイスに感謝します。アセンブリでアルゴリズム全体を書くことが最善の選択であり、理にかなっていることは知っていますが、アセンブリでプログラミングを学ぶことは決してないので、私の能力を超えています。

ただし、アセンブリで内部ループを最適化するのは簡単です。

counts := make([][8]int32, numRowBytes)

for i, b = range byteSlice {
    if b == 0 {                  // more than half of elements in byteSlice is 0.
        continue
    }
    expand = _expand_byte[b]
    __mm_add_epi32_inplace_purego(&counts[i], expand)
}

// expands a byte into its bits
var _expand_byte = [256]*[8]int32{
    &[8]int32{0, 0, 0, 0, 0, 0, 0, 0},
    &[8]int32{0, 0, 0, 0, 0, 0, 0, 1},
    &[8]int32{0, 0, 0, 0, 0, 0, 1, 0},
    &[8]int32{0, 0, 0, 0, 0, 0, 1, 1},
    &[8]int32{0, 0, 0, 0, 0, 1, 0, 0},
    ...
}

のアセンブリバージョン__mm_add_epi32_inplace_purego(これで十分です)、またはループ全体の作成を手伝っていただけますか?前もって感謝します。

fuz:

実行する操作は、バイトの位置的人口カウントと呼ばれます。これは機械学習で使用されるよく知られた操作であり、この問題を解決するため高速アルゴリズムでいくつかの研究が行われています。

残念ながら、これらのアルゴリズムの実装はかなり複雑です。このため、私は実装がはるかに簡単であるが、他の方法の約半分のパフォーマンスしか得られないカスタムアルゴリズムを開発しました。ただし、10 GB /秒と測定された場合でも、以前に比べてかなり改善されているはずです。

このアルゴリズムのアイデアは、32バイトのグループから対応するビットを収集vpmovmskbしてから、対応するカウンターに追加されるスカラー人口カウントを取得することです。これにより、依存チェーンが短くなり、一貫したIPCである3に到達できます。

あなたのアルゴリズムと比較して、私のコードはビットの順序を逆にしていることに注意してください。これcountsは、必要に応じてアセンブリコードがアクセスする配列要素を編集することで変更できます。ただし、将来の読者のために、このコードには、最下位ビットがビット0と見なされる一般的な規則を残したいと思います。

ソースコード

完全なソースコードはgithubにあります。

アルゴリズムは2つのバリアントで提供され、「Intel(R)Xeon(R)W-2133 CPU @ 3.60GHz」として識別されるプロセッサを搭載したマシンでテストされています。

位置人口は一度に32バイトをカウントします。

最高のパフォーマンスを得るために、カウンターは汎用レジスターに保持されます。ストリーミング動作を改善するために、メモリは事前に十分にプリフェッチされています。スカラーテールは、非常に単純なSHRL/ ADCL組み合わせを使用して処理されます最大11 GB /秒のパフォーマンスが達成されます。

#include "textflag.h"

// func PospopcntReg(counts *[8]int32, buf []byte)
TEXT ·PospopcntReg(SB),NOSPLIT,$0-32
    MOVQ counts+0(FP), DI
    MOVQ buf_base+8(FP), SI     // SI = &buf[0]
    MOVQ buf_len+16(FP), CX     // CX = len(buf)

    // load counts into register R8--R15
    MOVL 4*0(DI), R8
    MOVL 4*1(DI), R9
    MOVL 4*2(DI), R10
    MOVL 4*3(DI), R11
    MOVL 4*4(DI), R12
    MOVL 4*5(DI), R13
    MOVL 4*6(DI), R14
    MOVL 4*7(DI), R15

    SUBQ $32, CX            // pre-subtract 32 bit from CX
    JL scalar

vector: VMOVDQU (SI), Y0        // load 32 bytes from buf
    PREFETCHT0 384(SI)      // prefetch some data
    ADDQ $32, SI            // advance SI past them

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R15            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R14            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R13            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R12            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R11            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R10            // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R9         // add to counter
    VPADDD Y0, Y0, Y0       // shift Y0 left by one place

    VPMOVMSKB Y0, AX        // move MSB of Y0 bytes to AX
    POPCNTL AX, AX          // count population of AX
    ADDL AX, R8         // add to counter

    SUBQ $32, CX
    JGE vector          // repeat as long as bytes are left

scalar: ADDQ $32, CX            // undo last subtraction
    JE done             // if CX=0, there's nothing left

loop:   MOVBLZX (SI), AX        // load a byte from buf
    INCQ SI             // advance past it

    SHRL $1, AX         // CF=LSB, shift byte to the right
    ADCL $0, R8         // add CF to R8

    SHRL $1, AX
    ADCL $0, R9         // add CF to R9

    SHRL $1, AX
    ADCL $0, R10            // add CF to R10

    SHRL $1, AX
    ADCL $0, R11            // add CF to R11

    SHRL $1, AX
    ADCL $0, R12            // add CF to R12

    SHRL $1, AX
    ADCL $0, R13            // add CF to R13

    SHRL $1, AX
    ADCL $0, R14            // add CF to R14

    SHRL $1, AX
    ADCL $0, R15            // add CF to R15

    DECQ CX             // mark this byte as done
    JNE loop            // and proceed if any bytes are left

    // write R8--R15 back to counts
done:   MOVL R8, 4*0(DI)
    MOVL R9, 4*1(DI)
    MOVL R10, 4*2(DI)
    MOVL R11, 4*3(DI)
    MOVL R12, 4*4(DI)
    MOVL R13, 4*5(DI)
    MOVL R14, 4*6(DI)
    MOVL R15, 4*7(DI)

    VZEROUPPER          // restore SSE-compatibility
    RET

位置人口はCSAで一度に96バイトをカウントします

このバリアントは、上記のすべての最適化を実行しますが、事前に単一のCSAステップを使用して96バイトを64に削減します。予想どおり、これによりパフォーマンスが約30%向上し、最大16 GB /秒を達成します。

#include "textflag.h"

// func PospopcntRegCSA(counts *[8]int32, buf []byte)
TEXT ·PospopcntRegCSA(SB),NOSPLIT,$0-32
    MOVQ counts+0(FP), DI
    MOVQ buf_base+8(FP), SI     // SI = &buf[0]
    MOVQ buf_len+16(FP), CX     // CX = len(buf)

    // load counts into register R8--R15
    MOVL 4*0(DI), R8
    MOVL 4*1(DI), R9
    MOVL 4*2(DI), R10
    MOVL 4*3(DI), R11
    MOVL 4*4(DI), R12
    MOVL 4*5(DI), R13
    MOVL 4*6(DI), R14
    MOVL 4*7(DI), R15

    SUBQ $96, CX            // pre-subtract 32 bit from CX
    JL scalar

vector: VMOVDQU (SI), Y0        // load 96 bytes from buf into Y0--Y2
    VMOVDQU 32(SI), Y1
    VMOVDQU 64(SI), Y2
    ADDQ $96, SI            // advance SI past them
    PREFETCHT0 320(SI)
    PREFETCHT0 384(SI)

    VPXOR Y0, Y1, Y3        // first adder: sum
    VPAND Y0, Y1, Y0        // first adder: carry out
    VPAND Y2, Y3, Y1        // second adder: carry out
    VPXOR Y2, Y3, Y2        // second adder: sum (full sum)
    VPOR Y0, Y1, Y0         // full adder: carry out

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R15

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R14

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R13

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R12

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R11

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R10

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    VPADDB Y0, Y0, Y0       // shift carry out bytes left
    VPADDB Y2, Y2, Y2       // shift sum bytes left
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R9

    VPMOVMSKB Y0, AX        // MSB of carry out bytes
    VPMOVMSKB Y2, DX        // MSB of sum bytes
    POPCNTL AX, AX          // carry bytes population count
    POPCNTL DX, DX          // sum bytes population count
    LEAL (DX)(AX*2), AX     // sum popcount plus 2x carry popcount
    ADDL AX, R8

    SUBQ $96, CX
    JGE vector          // repeat as long as bytes are left

scalar: ADDQ $96, CX            // undo last subtraction
    JE done             // if CX=0, there's nothing left

loop:   MOVBLZX (SI), AX        // load a byte from buf
    INCQ SI             // advance past it

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R8         // add it to R8

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R9         // add it to R9

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R10            // add it to R10

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R11            // add it to R11

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R12            // add it to R12

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R13            // add it to R13

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R14            // add it to R14

    SHRL $1, AX         // is bit 0 set?
    ADCL $0, R15            // add it to R15

    DECQ CX             // mark this byte as done
    JNE loop            // and proceed if any bytes are left

    // write R8--R15 back to counts
done:   MOVL R8, 4*0(DI)
    MOVL R9, 4*1(DI)
    MOVL R10, 4*2(DI)
    MOVL R11, 4*3(DI)
    MOVL R12, 4*4(DI)
    MOVL R13, 4*5(DI)
    MOVL R14, 4*6(DI)
    MOVL R15, 4*7(DI)

    VZEROUPPER          // restore SSE-compatibility
    RET

ベンチマーク

以下は、2つのアルゴリズムのベンチマークと、純粋なGoでの単純なリファレンス実装です。完全なベンチマークはgithubリポジトリにあります。

BenchmarkReference/10-12    12448764            80.9 ns/op   123.67 MB/s
BenchmarkReference/32-12     4357808           258 ns/op     124.25 MB/s
BenchmarkReference/1000-12            151173          7889 ns/op     126.76 MB/s
BenchmarkReference/2000-12             68959         15774 ns/op     126.79 MB/s
BenchmarkReference/4000-12             36481         31619 ns/op     126.51 MB/s
BenchmarkReference/10000-12            14804         78917 ns/op     126.72 MB/s
BenchmarkReference/100000-12            1540        789450 ns/op     126.67 MB/s
BenchmarkReference/10000000-12            14      77782267 ns/op     128.56 MB/s
BenchmarkReference/1000000000-12           1    7781360044 ns/op     128.51 MB/s
BenchmarkReg/10-12                  49255107            24.5 ns/op   407.42 MB/s
BenchmarkReg/32-12                  186935192            6.40 ns/op 4998.53 MB/s
BenchmarkReg/1000-12                 8778610           115 ns/op    8677.33 MB/s
BenchmarkReg/2000-12                 5358495           208 ns/op    9635.30 MB/s
BenchmarkReg/4000-12                 3385945           357 ns/op    11200.23 MB/s
BenchmarkReg/10000-12                1298670           901 ns/op    11099.24 MB/s
BenchmarkReg/100000-12                115629          8662 ns/op    11544.98 MB/s
BenchmarkReg/10000000-12                1270        916817 ns/op    10907.30 MB/s
BenchmarkReg/1000000000-12                12      93609392 ns/op    10682.69 MB/s
BenchmarkRegCSA/10-12               48337226            23.9 ns/op   417.92 MB/s
BenchmarkRegCSA/32-12               12843939            80.2 ns/op   398.86 MB/s
BenchmarkRegCSA/1000-12              7175629           150 ns/op    6655.70 MB/s
BenchmarkRegCSA/2000-12              3988408           295 ns/op    6776.20 MB/s
BenchmarkRegCSA/4000-12              3016693           382 ns/op    10467.41 MB/s
BenchmarkRegCSA/10000-12             1810195           642 ns/op    15575.65 MB/s
BenchmarkRegCSA/100000-12             191974          6229 ns/op    16053.40 MB/s
BenchmarkRegCSA/10000000-12             1622        698856 ns/op    14309.10 MB/s
BenchmarkRegCSA/1000000000-12             16      68540642 ns/op    14589.88 MB/s

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

Python:csvを解析し、サブセットのみの値をカウントするのに最適な方法

分類Dev

アセンブリプログラミング64ビットを使用してLinuxでシャットダウン呼び出しを使用する方法は?

分類Dev

ビット単位またはポインタアドレスを使用して適用することの意味

分類Dev

アンカー(ビュー)に複数のリストポップアップウィンドウを追加し、各リストポップアップウィンドウをアンカーに配置するにはどうすればよいですか?

分類Dev

MacOSはすべてのアプリのウィンドウ位置をリセットします

分類Dev

アセンブリを使用して、バイトの位置にビットを挿入します

分類Dev

ある位置以下のセットビットをカウントする効率的な方法は何ですか?

分類Dev

Javaのみを使用してプライベートリポジトリからGitHubリリースアセットをダウンロードする方法

分類Dev

この場合、Pythonを使用してセットマッチングを最適化するにはどうすればよいですか?

分類Dev

別々のデータセットを使用して別々のFreeNASjail /プラグインをセットアップする最良の方法は何ですか?

分類Dev

グーグルマップクライアントJavaAPIを使用してグーグルマップのルートを最適化する方法。

分類Dev

サービスアカウントを使用してアップロードする際にGoogleドライブの保存超えた問題を修正する方法

分類Dev

オンタッチリスナーを使用してレイアウトのアルファ「カラー」を非アクティブ化する方法

分類Dev

このコードを最適化する方法(カウントソート)

分類Dev

Wojciech Mulaアルゴリズムを使用して、__ m256iをポップカウントし、結果を464ビットではなく32ビットワードに格納することは可能ですか?

分類Dev

セカンダリドライブへの増分バックアップに対してRAID1構成を使用することのポイントは何ですか?

分類Dev

ブラウザがHTML5ドラッグアンドドロップをサポートしていない場合にドラッグアンドドロップをサポートするための最良の方法は何ですか?

分類Dev

サービスアカウントの認証情報を使用してGoogleCloud StorageNodeJSクライアントライブラリを初期化する方法

分類Dev

Pythonリストのすべてのサブセットをn個のビンにビン化する方法

分類Dev

サービスアカウントの認証情報を使用してGoogleドライブにファイルをアップロードする方法

分類Dev

GNU Makeを使用してサブディレクトリの並列ビルドを最適化する方法は?

分類Dev

自動ホットキーを使用して、グループ内の最後にアクティブなウィンドウをアクティブ化する

分類Dev

単純なs3バケットポリシーを使用して、暗号化されたバケットへのクロスアカウントアクセスを許可するにはどうすればよいですか?

分類Dev

4文字のスポット内でカウントアップする数値を使用してディレクトリを作成するphpループ

分類Dev

HughesPJのプリティプリントライブラリを使用してブロックを適切にインデントする方法は?

分類Dev

HughesPJのプリティプリントライブラリを使用してブロックを適切にインデントする方法は?

分類Dev

最初のビットを検索し、bsf、blfを使用して最後のビットアセンブリを検索します

分類Dev

Linq EF-最適化されたクエリを使用して、特定の顧客のすべてのアカウント、カード、ローンなどをデータベースから収集する方法は?

分類Dev

xcode8を使用してUnityプロジェクトビルドでアップルのmatch-oリンカーIDエラーを修正する方法

Related 関連記事

  1. 1

    Python:csvを解析し、サブセットのみの値をカウントするのに最適な方法

  2. 2

    アセンブリプログラミング64ビットを使用してLinuxでシャットダウン呼び出しを使用する方法は?

  3. 3

    ビット単位またはポインタアドレスを使用して適用することの意味

  4. 4

    アンカー(ビュー)に複数のリストポップアップウィンドウを追加し、各リストポップアップウィンドウをアンカーに配置するにはどうすればよいですか?

  5. 5

    MacOSはすべてのアプリのウィンドウ位置をリセットします

  6. 6

    アセンブリを使用して、バイトの位置にビットを挿入します

  7. 7

    ある位置以下のセットビットをカウントする効率的な方法は何ですか?

  8. 8

    Javaのみを使用してプライベートリポジトリからGitHubリリースアセットをダウンロードする方法

  9. 9

    この場合、Pythonを使用してセットマッチングを最適化するにはどうすればよいですか?

  10. 10

    別々のデータセットを使用して別々のFreeNASjail /プラグインをセットアップする最良の方法は何ですか?

  11. 11

    グーグルマップクライアントJavaAPIを使用してグーグルマップのルートを最適化する方法。

  12. 12

    サービスアカウントを使用してアップロードする際にGoogleドライブの保存超えた問題を修正する方法

  13. 13

    オンタッチリスナーを使用してレイアウトのアルファ「カラー」を非アクティブ化する方法

  14. 14

    このコードを最適化する方法(カウントソート)

  15. 15

    Wojciech Mulaアルゴリズムを使用して、__ m256iをポップカウントし、結果を464ビットではなく32ビットワードに格納することは可能ですか?

  16. 16

    セカンダリドライブへの増分バックアップに対してRAID1構成を使用することのポイントは何ですか?

  17. 17

    ブラウザがHTML5ドラッグアンドドロップをサポートしていない場合にドラッグアンドドロップをサポートするための最良の方法は何ですか?

  18. 18

    サービスアカウントの認証情報を使用してGoogleCloud StorageNodeJSクライアントライブラリを初期化する方法

  19. 19

    Pythonリストのすべてのサブセットをn個のビンにビン化する方法

  20. 20

    サービスアカウントの認証情報を使用してGoogleドライブにファイルをアップロードする方法

  21. 21

    GNU Makeを使用してサブディレクトリの並列ビルドを最適化する方法は?

  22. 22

    自動ホットキーを使用して、グループ内の最後にアクティブなウィンドウをアクティブ化する

  23. 23

    単純なs3バケットポリシーを使用して、暗号化されたバケットへのクロスアカウントアクセスを許可するにはどうすればよいですか?

  24. 24

    4文字のスポット内でカウントアップする数値を使用してディレクトリを作成するphpループ

  25. 25

    HughesPJのプリティプリントライブラリを使用してブロックを適切にインデントする方法は?

  26. 26

    HughesPJのプリティプリントライブラリを使用してブロックを適切にインデントする方法は?

  27. 27

    最初のビットを検索し、bsf、blfを使用して最後のビットアセンブリを検索します

  28. 28

    Linq EF-最適化されたクエリを使用して、特定の顧客のすべてのアカウント、カード、ローンなどをデータベースから収集する方法は?

  29. 29

    xcode8を使用してUnityプロジェクトビルドでアップルのmatch-oリンカーIDエラーを修正する方法

ホットタグ

アーカイブ