我试图弄清楚如何转置(在 AVX2 内在函数中)一个由 8 行 __m256i 寄存器组成的矩形矩阵,每行都包含 32x 8 位(字符)。

__m256i matrix[8]; //32x8bit integers

转置矩阵就像 32 行 8x 8 位整数,但我也可以用同样的方式表示它:8 行 __m256i,每行包含 4 行 64 位。

我知道 _mm256_shuffle_epi8 适合这种问题,但我不知道如何将它用于矩形矩阵的这种特殊情况。

最佳答案

这个想法是首先将 32 个字符的 __m256i 行视为 8 个 32 位整数。
那么矩阵 __m256i matrix[8] 可以看作是一个 8x8 的整数矩阵。
现在通过转置这个 8x8 整数矩阵来获得解决方案,然后是__m256i 寄存器中的一些字节混洗。

对于 8x8 整数转置,我们
使用 Z boson's answer 的“标准”8x8 浮点转置。
通过从 si256ps 的一些转换,反之亦然,我们可以将它用于我们的 8x8 整数情况。

下面的函数 transpose_matrix_32_8() 实现了 8x32 字符转置。

/*     gcc -O3 -m64 -Wall -mavx2 -march=haswell transpose8_32b.c     */
#include <immintrin.h>
#include <stdio.h>
void print_matrix(__m256i*, int rows, int cols);

inline __m256i permute_row(__m256i row){
    row = _mm256_shuffle_epi8(row, _mm256_set_epi8(15,11,7,3 ,14,10,6,2 ,13,9,5,1 ,12,8,4,0   ,15,11,7,3 ,14,10,6,2 ,13,9,5,1 ,12,8,4,0));
    row = _mm256_permutevar8x32_epi32(row, _mm256_set_epi32(7,3,6,2,5,1,4,0));
    return row;
}

void transpose_matrix_32_8(__m256i* matrix){
            /* Start with 8x8 _epi32 transpose by casting the epi32 (si256) to _ps and use        */
            /* the 8x8 float transpose from https://stackoverflow.com/a/25627536/2439725          */
            /* See that answer for alternative 8x8 float transposes with slightly less shuffles.  */
    __m256  row0  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[0]));
    __m256  row1  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[1]));
    __m256  row2  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[2]));
    __m256  row3  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[3]));
    __m256  row4  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[4]));
    __m256  row5  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[5]));
    __m256  row6  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[6]));
    __m256  row7  = _mm256_castsi256_ps(_mm256_loadu_si256(&matrix[7]));
    __m256  __t0  = _mm256_unpacklo_ps(row0, row1);
    __m256  __t1  = _mm256_unpackhi_ps(row0, row1);
    __m256  __t2  = _mm256_unpacklo_ps(row2, row3);
    __m256  __t3  = _mm256_unpackhi_ps(row2, row3);
    __m256  __t4  = _mm256_unpacklo_ps(row4, row5);
    __m256  __t5  = _mm256_unpackhi_ps(row4, row5);
    __m256  __t6  = _mm256_unpacklo_ps(row6, row7);
    __m256  __t7  = _mm256_unpackhi_ps(row6, row7);
    __m256  __tt0 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(1,0,1,0));
    __m256  __tt1 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(3,2,3,2));
    __m256  __tt2 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(1,0,1,0));
    __m256  __tt3 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(3,2,3,2));
    __m256  __tt4 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(1,0,1,0));
    __m256  __tt5 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(3,2,3,2));
    __m256  __tt6 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(1,0,1,0));
    __m256  __tt7 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(3,2,3,2));
            row0  = _mm256_permute2f128_ps(__tt0, __tt4, 0x20);
            row1  = _mm256_permute2f128_ps(__tt1, __tt5, 0x20);
            row2  = _mm256_permute2f128_ps(__tt2, __tt6, 0x20);
            row3  = _mm256_permute2f128_ps(__tt3, __tt7, 0x20);
            row4  = _mm256_permute2f128_ps(__tt0, __tt4, 0x31);
            row5  = _mm256_permute2f128_ps(__tt1, __tt5, 0x31);
            row6  = _mm256_permute2f128_ps(__tt2, __tt6, 0x31);
            row7  = _mm256_permute2f128_ps(__tt3, __tt7, 0x31);
            /* End of 8x8 float transpose, cast the results back to _si256: */
    __m256i row0i = _mm256_castps_si256(row0);
    __m256i row1i = _mm256_castps_si256(row1);
    __m256i row2i = _mm256_castps_si256(row2);
    __m256i row3i = _mm256_castps_si256(row3);
    __m256i row4i = _mm256_castps_si256(row4);
    __m256i row5i = _mm256_castps_si256(row5);
    __m256i row6i = _mm256_castps_si256(row6);
    __m256i row7i = _mm256_castps_si256(row7);
            /* Now we only need a simple row permutation to get the right result: */
            row0i = permute_row(row0i);
            row1i = permute_row(row1i);
            row2i = permute_row(row2i);
            row3i = permute_row(row3i);
            row4i = permute_row(row4i);
            row5i = permute_row(row5i);
            row6i = permute_row(row6i);
            row7i = permute_row(row7i);
            _mm256_storeu_si256(&matrix[0], row0i);
            _mm256_storeu_si256(&matrix[1], row1i);
            _mm256_storeu_si256(&matrix[2], row2i);
            _mm256_storeu_si256(&matrix[3], row3i);
            _mm256_storeu_si256(&matrix[4], row4i);
            _mm256_storeu_si256(&matrix[5], row5i);
            _mm256_storeu_si256(&matrix[6], row6i);
            _mm256_storeu_si256(&matrix[7], row7i);
}


int main(){
    unsigned char input[256];
    __m256i matrix[8];

    /* Define char array with entries from 0 to 255 */
    for (int i=0;i<256;i++){
        input[i]=i;
    }

    /* Copy the char array to matrix                */
    for (int i=0;i<8;i++){
        matrix[i]=_mm256_loadu_si256((__m256i*)&input[i*32]);
    }

    print_matrix(matrix, 8, 32);
    transpose_matrix_32_8(matrix);
    print_matrix(matrix, 32, 8);

    return 0;
}


void print_matrix(__m256i* matrix, int rows, int cols){
    unsigned char* v;
    int i, j, k;
    /* Access matrix as chars */
    v = (unsigned char*)matrix;
    i = 0;
    /* Print the chars v[i] , i = 0, 1, 2, 3,..., 255                          */
    /* rows and cols only controls the positions of the new lines printf("\n") */
    for (k = 0; k < rows; k++){
        for (j = 0; j < cols; j++){
            printf("%4hhu", v[i]);
            i = i + 1;
        }
        printf("\n");
    }
    printf("\n");
}

输出是:
$ ./a.out
   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31
  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63
  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95
  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

   0  32  64  96 128 160 192 224
   1  33  65  97 129 161 193 225
   2  34  66  98 130 162 194 226
   3  35  67  99 131 163 195 227
   4  36  68 100 132 164 196 228
   5  37  69 101 133 165 197 229
   6  38  70 102 134 166 198 230
   7  39  71 103 135 167 199 231
   8  40  72 104 136 168 200 232
   9  41  73 105 137 169 201 233
  10  42  74 106 138 170 202 234
  11  43  75 107 139 171 203 235
  12  44  76 108 140 172 204 236
  13  45  77 109 141 173 205 237
  14  46  78 110 142 174 206 238
  15  47  79 111 143 175 207 239
  16  48  80 112 144 176 208 240
  17  49  81 113 145 177 209 241
  18  50  82 114 146 178 210 242
  19  51  83 115 147 179 211 243
  20  52  84 116 148 180 212 244
  21  53  85 117 149 181 213 245
  22  54  86 118 150 182 214 246
  23  55  87 119 151 183 215 247
  24  56  88 120 152 184 216 248
  25  57  89 121 153 185 217 249
  26  58  90 122 154 186 218 250
  27  59  91 123 155 187 219 251
  28  60  92 124 156 188 220 252
  29  61  93 125 157 189 221 253
  30  62  94 126 158 190 222 254
  31  63  95 127 159 191 223 255

这个 8x32 字节或字符转置需要 40 条“shuffle”指令。
这些 shuffle 指令都由 Intel Skylake 等 CPU 上的端口 5 处理。
这意味着吞吐量受到每 40 个 cpu 周期一个转置的限制。
如果周围的代码有很多指令,这不一定是问题
由其他执行端口执行。
但是,在某些情况下,更好地平衡整个工作
不同的执行端口。
另请参阅 Z boson's answer 以了解 8x8 的 2 个有趣的替代解决方案
transpose:这些替代方法需要更多的指令,但更少的洗牌。

下面是另一种 AVX2 8x32 字节或字符转置,只有 24 条“洗牌”指令,
但是有更多的加载、混合、插入和移位指令/微操作。
此解决方案的吞吐量在 Intel Skylake 上受到每 24 个 cpu 周期一个转置的限制。

inline __m256i permute_row_v3(__m256i row){
    row = _mm256_shuffle_epi8(row, _mm256_set_epi8(15,7,11,3 ,14,6,10,2 ,13,5,9,1 ,12,4,8,0   ,15,7,11,3 ,14,6,10,2 ,13,5,9,1 ,12,4,8,0));
    row = _mm256_permutevar8x32_epi32(row, _mm256_set_epi32(7,3,6,2,5,1,4,0));
    return row;
}


void transpose_matrix_32_8_v3(__m256i* matrix){
            /* Alternative 32x8 transpose with 24 shuffles but more instructions / micro-ops */
            __m128i* i_matrix = (__m128i*)matrix;
    __m256i w0 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 0])), _mm_loadu_si128(&i_matrix[ 8]), 1);
    __m256i w1 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 2])), _mm_loadu_si128(&i_matrix[10]), 1);
    __m256i w2 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 4])), _mm_loadu_si128(&i_matrix[12]), 1);
    __m256i w3 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 6])), _mm_loadu_si128(&i_matrix[14]), 1);
    __m256i w4 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 1])), _mm_loadu_si128(&i_matrix[ 9]), 1);
    __m256i w5 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 3])), _mm_loadu_si128(&i_matrix[11]), 1);
    __m256i w6 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 5])), _mm_loadu_si128(&i_matrix[13]), 1);
    __m256i w7 = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128(&i_matrix[ 7])), _mm_loadu_si128(&i_matrix[15]), 1);

    __m256i t0 = _mm256_unpacklo_epi64(w0, w1);
    __m256i t1 = _mm256_unpacklo_epi64(w2, w3);
    __m256i t2 = _mm256_unpackhi_epi64(w0, w1);
    __m256i t3 = _mm256_unpackhi_epi64(w2, w3);
    __m256i t4 = _mm256_unpacklo_epi64(w4, w5);
    __m256i t5 = _mm256_unpacklo_epi64(w6, w7);
    __m256i t6 = _mm256_unpackhi_epi64(w4, w5);
    __m256i t7 = _mm256_unpackhi_epi64(w6, w7);

            w0 = _mm256_srli_epi64(t0, 32);
            w1 = _mm256_slli_epi64(t1, 32);
            w2 = _mm256_srli_epi64(t2, 32);
            w3 = _mm256_slli_epi64(t3, 32);
            w4 = _mm256_srli_epi64(t4, 32);
            w5 = _mm256_slli_epi64(t5, 32);
            w6 = _mm256_srli_epi64(t6, 32);
            w7 = _mm256_slli_epi64(t7, 32);

    __m256i r0 = _mm256_blend_epi32(t0, w1, 0b10101010 );
    __m256i r1 = _mm256_blend_epi32(t1, w0, 0b01010101 );
    __m256i r2 = _mm256_blend_epi32(t2, w3, 0b10101010 );
    __m256i r3 = _mm256_blend_epi32(t3, w2, 0b01010101 );
    __m256i r4 = _mm256_blend_epi32(t4, w5, 0b10101010 );
    __m256i r5 = _mm256_blend_epi32(t5, w4, 0b01010101 );
    __m256i r6 = _mm256_blend_epi32(t6, w7, 0b10101010 );
    __m256i r7 = _mm256_blend_epi32(t7, w6, 0b01010101 );

            w0 = permute_row_v3(r0);
            w1 = permute_row_v3(r1);
            w2 = permute_row_v3(r2);
            w3 = permute_row_v3(r3);
            w4 = permute_row_v3(r4);
            w5 = permute_row_v3(r5);
            w6 = permute_row_v3(r6);
            w7 = permute_row_v3(r7);
              _mm256_storeu_si256(&matrix[0], w0);
              _mm256_storeu_si256(&matrix[1], w1);
              _mm256_storeu_si256(&matrix[2], w2);
              _mm256_storeu_si256(&matrix[3], w3);
              _mm256_storeu_si256(&matrix[4], w4);
              _mm256_storeu_si256(&matrix[5], w5);
              _mm256_storeu_si256(&matrix[6], w6);
              _mm256_storeu_si256(&matrix[7], w7);
}

关于c - 由 8x __m256i 寄存器表示的矩阵的 AVX2 转置,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55816432/

10-13 05:35