2013-01-28 11 views
11

Iは、2つの別個のアレイarray1array2のインターリーブされたバイトを含むバイトmixedの配列へのポインタを持っています。私はarray1 = abcd...array2 = 1234...を取得Cで最も速くデインターリーブ動作?

a1b2c3d4... 

私がする必要がある何ですかバイトをデインターリーブ:mixedはこのような何かが見えると言います。私はmixedの長さを事前に知っていて、長さはarray1array2と等しく、どちらもmixed/2に等しい。ここで

は(array1array2がすでに割り当てられている)私の現在の実装である:

int i, j; 
int mixedLength_2 = mixedLength/2; 
for (i = 0, j = 0; i < mixedLength_2; i++, j += 2) 
{ 
    array1[i] = mixed[j]; 
    array2[i] = mixed[j+1]; 
} 

これは、高価な乗算または除算演算を回避し、それでも十分に速く実行されません。 memcpyのようなものがあり、ローレベルのブロックコピー操作を使用して処理を高速化できるインデクサーが必要であると私は思っています。私が現在持っているものより速い実装がありますか?

編集

ターゲットプラットフォームはiOSとMac用のObjective-Cです。 iOSデバイスでは高速な操作が重要なので、特にiOSをターゲットとしたソリューションは何もないものより優れています。応答、特にスティーブンキヤノン、グラハム・リー、およびMeckiため

更新

みんなありがとう。ここでは、スティーブンスのNEON組み込み関数(使用可能な場合)を使用する「マスター」関数があります。それ以外の場合は、Meckiが提案する反復回数を減らしたGrahamのユニオンカーソルです。

void interleave(const uint8_t *srcA, const uint8_t *srcB, uint8_t *dstAB, size_t dstABLength) 
{ 
#if defined __ARM_NEON__ 
    // attempt to use NEON intrinsics 

    // iterate 32-bytes at a time 
    div_t dstABLength_32 = div(dstABLength, 32); 
    if (dstABLength_32.rem == 0) 
    { 
     while (dstABLength_32.quot --> 0) 
     { 
      const uint8x16_t a = vld1q_u8(srcA); 
      const uint8x16_t b = vld1q_u8(srcB); 
      const uint8x16x2_t ab = { a, b }; 
      vst2q_u8(dstAB, ab); 
      srcA += 16; 
      srcB += 16; 
      dstAB += 32; 
     } 
     return; 
    } 

    // iterate 16-bytes at a time 
    div_t dstABLength_16 = div(dstABLength, 16); 
    if (dstABLength_16.rem == 0) 
    { 
     while (dstABLength_16.quot --> 0) 
     { 
      const uint8x8_t a = vld1_u8(srcA); 
      const uint8x8_t b = vld1_u8(srcB); 
      const uint8x8x2_t ab = { a, b }; 
      vst2_u8(dstAB, ab); 
      srcA += 8; 
      srcB += 8; 
      dstAB += 16; 
     } 
     return; 
    } 
#endif 

    // if the bytes were not aligned properly 
    // or NEON is unavailable, fall back to 
    // an optimized iteration 

    // iterate 8-bytes at a time 
    div_t dstABLength_8 = div(dstABLength, 8); 
    if (dstABLength_8.rem == 0) 
    { 
     typedef union 
     { 
      uint64_t wide; 
      struct { uint8_t a1; uint8_t b1; uint8_t a2; uint8_t b2; uint8_t a3; uint8_t b3; uint8_t a4; uint8_t b4; } narrow; 
     } ab8x8_t; 

     uint64_t *dstAB64 = (uint64_t *)dstAB; 
     int j = 0; 
     for (int i = 0; i < dstABLength_8.quot; i++) 
     { 
      ab8x8_t cursor; 
      cursor.narrow.a1 = srcA[j ]; 
      cursor.narrow.b1 = srcB[j++]; 
      cursor.narrow.a2 = srcA[j ]; 
      cursor.narrow.b2 = srcB[j++]; 
      cursor.narrow.a3 = srcA[j ]; 
      cursor.narrow.b3 = srcB[j++]; 
      cursor.narrow.a4 = srcA[j ]; 
      cursor.narrow.b4 = srcB[j++]; 
      dstAB64[i] = cursor.wide; 
     } 
     return; 
    } 

    // iterate 4-bytes at a time 
    div_t dstABLength_4 = div(dstABLength, 4); 
    if (dstABLength_4.rem == 0) 
    { 
     typedef union 
     { 
      uint32_t wide; 
      struct { uint8_t a1; uint8_t b1; uint8_t a2; uint8_t b2; } narrow; 
     } ab8x4_t; 

     uint32_t *dstAB32 = (uint32_t *)dstAB; 
     int j = 0; 
     for (int i = 0; i < dstABLength_4.quot; i++) 
     { 
      ab8x4_t cursor; 
      cursor.narrow.a1 = srcA[j ]; 
      cursor.narrow.b1 = srcB[j++]; 
      cursor.narrow.a2 = srcA[j ]; 
      cursor.narrow.b2 = srcB[j++]; 
      dstAB32[i] = cursor.wide; 
     } 
     return; 
    } 

    // iterate 2-bytes at a time 
    div_t dstABLength_2 = div(dstABLength, 2); 
    typedef union 
    { 
     uint16_t wide; 
     struct { uint8_t a; uint8_t b; } narrow; 
    } ab8x2_t; 

    uint16_t *dstAB16 = (uint16_t *)dstAB; 
    for (int i = 0; i < dstABLength_2.quot; i++) 
    { 
     ab8x2_t cursor; 
     cursor.narrow.a = srcA[i]; 
     cursor.narrow.b = srcB[i]; 
     dstAB16[i] = cursor.wide; 
    } 
} 

void deinterleave(const uint8_t *srcAB, uint8_t *dstA, uint8_t *dstB, size_t srcABLength) 
{ 
#if defined __ARM_NEON__ 
    // attempt to use NEON intrinsics 

    // iterate 32-bytes at a time 
    div_t srcABLength_32 = div(srcABLength, 32); 
    if (srcABLength_32.rem == 0) 
    { 
     while (srcABLength_32.quot --> 0) 
     { 
      const uint8x16x2_t ab = vld2q_u8(srcAB); 
      vst1q_u8(dstA, ab.val[0]); 
      vst1q_u8(dstB, ab.val[1]); 
      srcAB += 32; 
      dstA += 16; 
      dstB += 16; 
     } 
     return; 
    } 

    // iterate 16-bytes at a time 
    div_t srcABLength_16 = div(srcABLength, 16); 
    if (srcABLength_16.rem == 0) 
    { 
     while (srcABLength_16.quot --> 0) 
     { 
      const uint8x8x2_t ab = vld2_u8(srcAB); 
      vst1_u8(dstA, ab.val[0]); 
      vst1_u8(dstB, ab.val[1]); 
      srcAB += 16; 
      dstA += 8; 
      dstB += 8; 
     } 
     return; 
    } 
#endif 

    // if the bytes were not aligned properly 
    // or NEON is unavailable, fall back to 
    // an optimized iteration 

    // iterate 8-bytes at a time 
    div_t srcABLength_8 = div(srcABLength, 8); 
    if (srcABLength_8.rem == 0) 
    { 
     typedef union 
     { 
      uint64_t wide; 
      struct { uint8_t a1; uint8_t b1; uint8_t a2; uint8_t b2; uint8_t a3; uint8_t b3; uint8_t a4; uint8_t b4; } narrow; 
     } ab8x8_t; 

     uint64_t *srcAB64 = (uint64_t *)srcAB; 
     int j = 0; 
     for (int i = 0; i < srcABLength_8.quot; i++) 
     { 
      ab8x8_t cursor; 
      cursor.wide = srcAB64[i]; 
      dstA[j ] = cursor.narrow.a1; 
      dstB[j++] = cursor.narrow.b1; 
      dstA[j ] = cursor.narrow.a2; 
      dstB[j++] = cursor.narrow.b2; 
      dstA[j ] = cursor.narrow.a3; 
      dstB[j++] = cursor.narrow.b3; 
      dstA[j ] = cursor.narrow.a4; 
      dstB[j++] = cursor.narrow.b4; 
     } 
     return; 
    } 

    // iterate 4-bytes at a time 
    div_t srcABLength_4 = div(srcABLength, 4); 
    if (srcABLength_4.rem == 0) 
    { 
     typedef union 
     { 
      uint32_t wide; 
      struct { uint8_t a1; uint8_t b1; uint8_t a2; uint8_t b2; } narrow; 
     } ab8x4_t; 

     uint32_t *srcAB32 = (uint32_t *)srcAB; 
     int j = 0; 
     for (int i = 0; i < srcABLength_4.quot; i++) 
     { 
      ab8x4_t cursor; 
      cursor.wide = srcAB32[i]; 
      dstA[j ] = cursor.narrow.a1; 
      dstB[j++] = cursor.narrow.b1; 
      dstA[j ] = cursor.narrow.a2; 
      dstB[j++] = cursor.narrow.b2; 
     } 
     return; 
    } 

    // iterate 2-bytes at a time 
    div_t srcABLength_2 = div(srcABLength, 2); 
    typedef union 
    { 
     uint16_t wide; 
     struct { uint8_t a; uint8_t b; } narrow; 
    } ab8x2_t; 

    uint16_t *srcAB16 = (uint16_t *)srcAB; 
    for (int i = 0; i < srcABLength_2.quot; i++) 
    { 
     ab8x2_t cursor; 
     cursor.wide = srcAB16[i]; 
     dstA[i] = cursor.narrow.a; 
     dstB[i] = cursor.narrow.b; 
    } 
} 
+0

入力が実際にインターリーブされている場合は、実際にブロックコピーできません。 –

+0

あなたはどのプラットフォームをターゲットにしていますか?多くは、これらの操作を実行するために最適化されたライブラリ関数を備えています。しかし、C標準ライブラリには何もありません。 –

+0

@StephenCanon:iOS/MacのObjective-Cです。この最適化はiOSにとって特に重要です。 – Anton

答えて

8

私の頭の上には、2チャンネルのバイトデータをデインターリーブするライブラリ関数がありません。しかし、Appleにそのような機能を要求するためのバグレポートを提出する価値があります。一方

、それはNEONまたはSSEの組み込み関数を使用して、このような機能をベクトル化するために非常に簡単です。特に、ARMでは、vld1q_u8を使用して各ソース配列からベクトルをロードし、vuzpq_u8をデインタリーブし、得られたベクトルを格納するためにvst1q_u8を使用します。ここではテストしていない、あるいは作成しようとしている大まかなスケッチがありますが、その一般的な考え方を説明する必要があります。より洗練された実装は、特に、NEONコンパイラはこれでないことがあり、単一の命令にストア/ 16Bのレジスタをロードすることができ、そしてパイプラインのいくつかの量および/または展開がどのくらいに応じて有益であるかもしれない(確かに可能ですあなたのバッファが)以下のとおりです。

#if defined __ARM_NEON__ 
# include <arm_neon.h> 
#endif 
#include <stdint.h> 
#include <stddef.h> 

void deinterleave(uint8_t *mixed, uint8_t *array1, uint8_t *array2, size_t mixedLength) { 
#if defined __ARM_NEON__ 
    size_t vectors = mixedLength/32; 
    mixedLength %= 32; 
    while (vectors --> 0) { 
     const uint8x16_t src0 = vld1q_u8(mixed); 
     const uint8x16_t src1 = vld1q_u8(mixed + 16); 
     const uint8x16x2_t dst = vuzpq_u8(src0, src1); 
     vst1q_u8(array1, dst.val[0]); 
     vst1q_u8(array2, dst.val[1]); 
     mixed += 32; 
     array1 += 16; 
     array2 += 16; 
    } 
#endif 
    for (size_t i=0; i<mixedLength/2; ++i) { 
     array1[i] = mixed[2*i]; 
     array2[i] = mixed[2*i + 1]; 
    } 
} 
+0

関連する型が 'float'と' int'であっても、 'float'ベクトル命令を使って' int'sをシャッフルし、Accelerateフレームワークと同じくらい多くのプラットフォームを乗算すると、この質問のOPと同じ心配があります〜のためです。その答えは、x86アーキテクチャだけでは微妙です。 http://stackoverflow.com/questions/4996384/do-i-get-a-performance-penalty-when-mixing-sse-integer-float-simd-instructions –

+0

@ PascalCuoq:それは実際問題ではないでしょう。データは完全にFPとして扱われるため、ドメイン横断の罰則はありません。しかし、それは議論のポイントです。 –

+0

うわー、NEONの組み込み関数はすごく高速です。私はvuzpq_u8なしでvld2q_u8とvst1q_u8を使用しています。 – Anton

3

私は軽く、これをテストしてみたが、それは少なくとも二倍の速あなたのバージョンと思えた:私は、構造体パッキングには注意しなかった

typedef union { 
uint16_t wide; 
struct { uint8_t top; uint8_t bottom; } narrow; 
} my_union; 

uint16_t *source = (uint16_t *)mixed; 
for (int i = 0; i < mixedLength/2; i++) 
{ 
    my_union cursor; 
    cursor.wide = source[i]; 
    array1[i] = cursor.narrow.top; 
    array2[i] = cursor.narrow.bottom; 
} 

お知らせしますが、この場合、このアーキテクチャのそれは問題ではありません。誰かが私の選択であるtopbottomに文句を言うかもしれません。私はあなたが必要とする整数の半分を知っていると思います。

+1

なぜこのバージョンがより速くなるのか混乱しています。確かに何が起こっているのかわかりません。 –

+0

それは組合を巧みに使い、反復ごとの操作数を減らすのに良い方法です...私はそれが好きです。 – Anton

+0

なぜあなたは組合が必要ですか?構造体を使うだけで、まったく同じ効果が得られます。 – Mecki

2

さて、ここであなたのオリジナルの方法であって、千万のエントリと-O3

static void simpleDeint (
    uint8_t * array1, uint8_t * array2, uint8_t * mixed, int mixedLength 
) { 
    int i, j; 
    int mixedLength_2 = mixedLength/2; 
    for (i = 0, j = 0; i < mixedLength_2; i++, j += 2) 
    { 
     array1[i] = mixed[j]; 
     array2[i] = mixed[j+1]; 
    } 
} 

(コンパイラは、最大速度を最適化しなければならない)、私は私のMac上で毎秒この154回を実行することができます。私は、毎秒193の実行を取得し、以前のように

static void structDeint (
    uint8_t * array1, uint8_t * array2, uint8_t * mixed, int mixedLength 
) { 
    int i; 
    int len; 
    uint8_t * array1Ptr = (uint8_t *)array1; 
    uint8_t * array2Ptr = (uint8_t *)array2; 
    struct { 
     uint8_t byte1; 
     uint8_t byte2; 
    } * tb = (void *)mixed; 

    len = mixedLength/2; 
    for (i = 0; i < len; i++) { 
     *(array1Ptr++) = tb->byte1; 
     *(array2Ptr++) = tb->byte2; 
     tb++; 
    } 
} 

同じカウントと最適化:

は、ここに私の最初の提案です。今

グラハム・リーからの提案:

static void unionDeint (
    uint8_t * array1, uint8_t * array2, uint8_t * mixed, int mixedLength 
) { 
    union my_union { 
     uint16_t wide; 
     struct { uint8_t top; uint8_t bottom; } narrow; 
    }; 

    uint16_t * source = (uint16_t *)mixed; 
    for (int i = 0; i < mixedLength/2; i++) { 
     union my_union cursor; 
     cursor.wide = source[i]; 
     array1[i] = cursor.narrow.top; 
     array2[i] = cursor.narrow.bottom; 
    } 
} 

同じセットアップ前と同じように、198回の実行毎秒(注:このメソッドは、安全エンディアンではない、結果はあなたのケースの配列1にはCPUのエンディアンに依存し、配列2は、おそらくARMはリトルエンディアンであるため、スワップされているので、コード内でスワップする必要があります)。

はここで、これまで私の最高の一つだ:上記のように

static void uint32Deint (
    uint8_t * array1, uint8_t * array2, uint8_t * mixed, int mixedLength 
) { 
    int i; 
    int count; 
    uint32_t * fourBytes = (void *)mixed; 
    uint8_t * array1Ptr = (uint8_t *)array1; 
    uint8_t * array2Ptr = (uint8_t *)array2; 


    count = mixedLength/4; 
    for (i = 0; i < count; i++) { 
     uint32_t temp = *(fourBytes++); 

#if __LITTLE_ENDIAN__ 
     *(array1Ptr++) = (uint8_t)(temp & 0xFF); 
     temp >>= 8; 
     *(array2Ptr++) = (uint8_t)(temp & 0xFF); 
     temp >>= 8; 
     *(array1Ptr++) = (uint8_t)(temp & 0xFF); 
     temp >>= 8; 
     *(array2Ptr++) = tb->byte2; 

#else 
     *(array1Ptr++) = (uint8_t)(temp >> 24); 
     *(array2Ptr++) = (uint8_t)((temp >> 16) & 0xFF); 
     *(array1Ptr++) = (uint8_t)((temp >> 8) & 0xFF); 
     *(array2Ptr++) = (uint8_t)(temp & 0xFF); 
#endif 
    } 
    // Either it is a multiple of 4 or a multiple of 2. 
    // If it is a multiple of 2, 2 bytes are left over. 
    if (count * 4 != mixedLength) { 
     *(array1Ptr) = mixed[mixedLength - 2]; 
     *(array2Ptr) = mixed[mixedLength - 1]; 
    } 
} 

同じセットアップ、219回二、私はミスを犯した場合を除き、エンディアンのどちらかで動作するはずです。

-1
  1. 時期尚早の最適化は

  2. あなたのコンパイラは、おそらくあなたがより最適化に優れている悪いです。

    はあなたがコンパイラが持つことができないことを、あなたのデータの意味的な知識を持っているので、コンパイラを助けるために行うことができますものがある、と述べた

  1. は、できるだけ多くのバイトを読み取りおよび書き込み可能であれば、ネイティブワードサイズまで - メモリ操作は高価ですので、可能な限りレジスタ内で操作してください。

  2. アンロールループ - 「Duff's Device」を調べます。

FWIW、私はCコード(まだシンプルながらも)ほとんどが「最適」検討するものを使用してコピーループの2つのバージョン、あなたと1とほぼ同じ、第二の生産:

void test1(byte *p, byte *p1, byte *p2, int n) 
{ 
    int i, j; 
    for (i = 0, j = 0; i < n/2; i++, j += 2) { 
     p1[i] = p[j]; 
     p2[i] = p[j + 1]; 
    } 
} 

void test2(byte *p, byte *p1, byte *p2, int n) 
{ 
    while (n) { 
     *p1++ = *p++; 
     *p2++ = *p++; 
     n--; n--; 
    } 
} 

Intel x86上のgcc -O3 -Sでは、どちらもほぼ同じアセンブリコードが生成されていました。ここでは内部ループしている:最初のバージョンはn/2までカウントし、第二のカウントがゼロにまでので

LBB1_2: 
    movb -1(%rdi), %al 
    movb %al, (%rsi) 
    movb (%rdi), %al 
    movb %al, (%rdx) 
    incq %rsi 
    addq $2, %rdi 
    incq %rdx 
    decq %rcx 
    jne LBB1_2 

LBB2_2: 
    movb -1(%rdi), %al 
    movb %al, (%rsi) 
    movb (%rdi), %al 
    movb %al, (%rdx) 
    incq %rsi 
    addq $2, %rdi 
    incq %rdx 
    addl $-2, %ecx 
    jne LBB2_2 

の両方が同じ命令数が、違いはもっぱらを占めています。その結果

/* non-portable - assumes little endian */ 
void test3(byte *p, byte *p1, byte *p2, int n) 
{ 
    ushort *ps = (ushort *)p; 

    n /= 2; 
    while (n) { 
     ushort n = *ps++; 
     *p1++ = n; 
     *p2++ = n >> 8; 
    } 
} 

LBB3_2: 
    movzwl (%rdi), %ecx 
    movb %cl, (%rsi) 
    movb %ch, (%rdx) # NOREX 
    addq $2, %rdi 
    incq %rsi 
    incq %rdx 
    decq %rax 
    jne LBB3_2 

それは%cl%chへの即時アクセスを利用していますので、1つの少なく命令である

EDITはここより良いバージョンです。

+0

理論的には、私は同意します。コンパイラがコードを最適化します。これは、計算時間がわずかに短縮されても、システム性能に重大な影響を及ぼす可能性のある、1%未満の低レベルのケースの1つです。 – Anton

+1

"十分に速く実行されません"とは、最適化が時期尚早ではないことを示します。 –

1

Grahamのソリューションをお勧めしますが、これが本当にスピードクリティカルで、あなたがアセンブラーに向かおうと思えば、さらに高速化することができます。

  1. 読むmixedから全体の32ビット整数

    アイデアがこれです。あなたは 'a1b2'を得ます。

  2. 下位16ビットを8ビット回転させて '1ab2'とします(これはARMのデフォルトであり、したがってApple A#の最初の2バイトが下位バイトなので、リトルエンディアンを使用しています)。

  3. 32ビットレジスタ全体を(私は正しいと思いますが)8ビットだけ回転させて '21ab'を得ます。

  4. は '12AB'

  5. を取得するために、8ビットで下位16ビットを回してarray2に下位8ビットを書き込みます。

  6. 32ビットレジスタ全体を16ビット回転させます。

  7. array1

  8. 16ビットによって予めarray1、16ビットによってarray2、及び32ビットによりmixedに下位8ビットを書きます。

  9. 繰り返し。

我々は、二つのメモリを書き込み、4つのレジスタ操作2メモリは、(我々はグラハムのバージョンまたは同等のものを使用と仮定)を読み取り取引1回のメモリの読み出しと4メモリーいます。操作の数が6から7に増加したのに対し、レジスタ操作はメモリ操作より高速です。したがって、それはより効率的です。また、16ビットではなく、mixedから32ビットを一度に読み込むので、繰り返し管理を半分に削減しました。

PS:これは理論的には64ビットアーキテクチャでも実行できますが、 'a1b2c3d4'のすべてのローテーションを実行すると、あなたは狂気に追い込まれます。

+3

アセンブリを使用する場合は、なぜSIMD命令を使用しないのですか?これは非常に高速でしょうか? –

+1

主に、それらを使用するのに十分なアセンブラを学んだことがないためです。 –

0

x86 SSEでは、packpunpck命令が必要です。非破壊3オペランド命令の便宜のためにAVXを使用する例。 (256b pack/unpck命令は、128bレーンの低および高のレーンで2つの128bアンパックを実行するため、AVX2 256bワイド命令を使用しないため、最終的な順序で正しいものを得るためにシャッフルが必要です)。

次のイントリンシック版も同様に動作します。 Asmの指示は、簡単な答えを書くだけで入力するのが短くなります。

インターリーブabcd1234 - >a1b2c3d4

# loop body: 
vmovdqu (%rax), %xmm0 # load the sources 
vmovdqu (%rbx), %xmm1 
vpunpcklbw %xmm0, %xmm1, %xmm2 # low halves -> 128b reg 
vpunpckhbw %xmm0, %xmm2, %xmm3 # high halves -> 128b reg 
vmovdqu %xmm2, (%rdi) # store the results 
vmovdqu %xmm3, 16(%rdi) 
# blah blah some loop structure. 

`punpcklbw` interleaves the bytes in the low 64 of the two source `xmm` registers. There are `..wd` (word->dword), and dword->qword versions which would be useful for 16 or 32bit elements. 

デインターリーブa1b2c3d4 - >abcd1234

#outside the loop 
vpcmpeqb %xmm5, %xmm5 # set to all-1s 
vpsrlw  $8, %xmm5, %xmm5 # every 16b word has low 8b = 0xFF, high 8b = 0. 

# loop body 
vmovdqu (%rsi), %xmm2  # load two src chunks 
vmovdqu 16(%rsi), %xmm3 
vpand  %xmm2, %xmm5, %xmm0 # mask to leave only the odd bytes 
vpand  %xmm3, %xmm5, %xmm1 
vpackuswb %xmm0, %xmm1, %xmm4 
vmovdqu %xmm4, (%rax) # store 16B of a[] 
vpsrlw  $8, %xmm2, %xmm6  # even bytes -> odd bytes 
vpsrlw  $8, %xmm3, %xmm7 
vpackuswb %xmm6, %xmm7, %xmm4 
vmovdqu %xmm4, (%rbx) 

これはもちろん、たくさんの少ないレジスタを使用することができます。私は、パフォーマンスではなく読みやすさのためにレジスタを再利用することを避けました。ハードウェアレジスタ名の変更は、以前の値に依存しないものから始める限り、再利用を問題にしません。 pack命令は、符号付きまたは符号なし飽和ないため(例えばmovd、ないmovss又はpinsrd。)

デインタリーブはあまり多くの作業であるため、各16bは素子の上部8bは最初ゼロにしなければなりません。

代わりに、pshufbを使用して、単一のソースレジスタの奇数または偶数ワードをレジスタのローの64にパックすることができます。しかし、AMD XOP命令セットの外部では、VPPERMのように、一度に2つのレジスタからバイトを選択できるシャッフルはありません(Altivecの大好きなvpermなど)。したがって、SSE/AVXだけでは、128bのインターリーブされたデータごとに2つのシャッフルが必要になります。また、ストアポートの使用がボトルネックになる可能性があるので、aの2つの64ビットチャンクを1つのレジスタに結合して128bストアをセットアップすることはpunpckです。

AMD XOPでは、デインタリーブは2x128bの負荷、2 VPPERM、および2x128bのストアになります。

関連する問題