AVX2由8个__m256i寄存器表示的矩阵的转置

时间:2019-04-23 17:22:21

标签: c x86 transpose simd avx2

我试图找出如何转置(在AVX2内部函数中)矩形矩阵,该矩形矩阵由8行__m256i寄存器组成,每行包含32x 8位(字符)。

__m256i matrix[8]; //32x8bit integers

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

我知道_mm256_shuffle_epi8适用于此类问题,但我不知道如何在这种特殊的矩形矩阵情况下使用它。

1 个答案:

答案 0 :(得分:7)

想法是首先将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字节或char转置需要40条“ shuffle”指令。 这些随机播放指令均由cpus上的端口5(例如Intel Skylake)处理。 这意味着吞吐量受每40 cpu周期一个转置限制。 如果周围的代码有很多指令,这不一定是问题 由其他执行端口执行。 但是,在某些情况下,最好在整个 不同的执行端口。 另请参阅Z boson's answer,以获取8x8的2种有趣的替代解决方案 转置:这些替代方法需要更多的说明,但改组次数较少。

下面是仅24条“随机”指令的替代AVX2 8x32字节或char转置, 但具有更多的加载,混合,插入和移位指令/微型操作。 此解决方案的吞吐量受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);
}