微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在不使用AVX512的情况下将uint8_t或uint16_t数组部分加载到_m256i寄存器并用1填充剩余位的最快方法

如何解决在不使用AVX512的情况下将uint8_t或uint16_t数组部分加载到_m256i寄存器并用1填充剩余位的最快方法

基本上,我正在尝试将小于uint8_t寄存器的uint16_t__m256i数组加载到__m256i寄存器中并填充所有位在目标__m256i中,数组未填充1。

我想要使用AVX512的示例是:

#define ARR_SIZE_EPI8 (some_constant_value < 32)

// partial load for uint8_t
partial_load_epi8(uint8_t * arr) {
__m256i ones = _mm256_set1_epi64x(-1)
 return _mm256_mask_loadu_epi8(ones,(1 << ARR_SIZE_EPI8) - 1,arr);
}


#define ARR_SIZE_EPI16 (some_constant_value < 16)

// partial load for uin16_t
partial_load_epi16(uint16_t * arr) {
__m256i ones = _mm256_set1_epi64x(-1)
 return _mm256_mask_loadu_epi16(ones,(1 << ARR_SIZE_EPI16) - 1,arr);
}

如果AVX2我可以使用,则仅使用ARR_SIZE * sizeof(T) % sizeof(int) == 0

partial_load_epi16_avx2(uint16_t * arr) {

__m256i mask_vec = _mm256_set_epi32( /* proper values for ARR_SIZE_EPI16 elements */ );
__m256i fill_vec = _mm256_set_epi16( /* 1s until ARR_SIZE_EPI16 * sizeof(uint16_t) */ );
__m256i load_vec = _mm256_maskloadu_epi32((int32_t *)arr,mask_vec);
return _mm256_or_si256(load_vec,fill_vec);
}

这使用了大约.rodate,但是似乎并不昂贵。另一方面,当ARR_SIZE * sizeof(T) % sizeof(int) != 0uint16_tARR_SIZE_EPI16时,我能想到的最好的方法

partial_load_epi16_avx2_not_aligned(uint16_t * arr) {

__m256i mask_vec = _mm256_set_epi32( /* proper values for ARR_SIZE_EPI16 elements */ );
uint32_t tmp = 0xffff0000 | arr[ARR_SIZE_EPI16];
__m256i fill_vec = _mm256_set_epi32( /* 1s until ARR_SIZE_EPI16 * sizeof(uint16_t) / sizeof(int32_t) */,tmp,/* 0s */ );
__m256i load_vec = _mm256_maskloadu_epi32((int32_t *)arr,fill_vec);
}

// or

partial_load_epi16_avx_not_aligned(uint16_t * arr) {    
    __m256i fill_v = _mm256_set1_epi64x(-1);
    __m256i pload = _mm256_maskload_epi32((int32_t *)arr,_mm256_set_epi32( /* Assume proper mask */ ));
    fill_v = _mm256_insert_epi16(fill_v,arr[ARR_SIZE_EPI16],ARR_SIZE_EPI16);
    return _mm256_blend_epi32(fill_v,pload,(1 << ((ARR_SIZE_EPI16 / 2) - 1)));
}

添加vextractsi128vpinsrwvinsertsi128。我想知道是否有没有那么多开销的更好方法

谢谢!

编辑: 内存将由用户提供,我无法对是否可以访问arr之前或之后arr + ARR_SIZE进行任何假设。

用例:实现分类网络。实施2级幂的排序网络的指令通常比2级幂(尤其是字节/ 2字节值)的排序网络效率要高得多,因此我想做的是加载用户数组,然后填充它具有最大值(现在就做无符号的情况),这样我就可以将排序网络的大小四舍五入到2的下一个幂。

编辑: VPBLENDD和VPBLENDVB不能替代VMOVDQU

编辑: 有趣的是,我发现的最佳解决方案是将数组作为操作数3内联vpblendvb 请勿这样做

Edit2:

测试程序以查看vpblenddvpblendvb是否引起额外的页面错误

#include <immintrin.h>
#include <stdint.h>
#include <sys/mman.h>
#include <utility>

#define N 5


template<uint32_t... e>
constexpr __m256i inline __attribute__((always_inline))
load_N_kernel2(std::integer_sequence<uint32_t,e...> _e) {
    return _mm256_set_epi8(e...);
}


template<uint32_t... e>
constexpr __m256i inline __attribute__((always_inline))
load_N_kernel(std::integer_sequence<uint32_t,e...> _e) {
    return load_N_kernel2(
        std::integer_sequence<uint32_t,((((31 - e) / 4) < N) << 7)...>{});
}

constexpr __m256i inline __attribute__((always_inline)) load_N() {
    return load_N_kernel(std::make_integer_sequence<uint32_t,32>{});
}



__m256i __attribute__((noinline)) mask_load(uint32_t * arr) {
    __m256i tmp;
    return _mm256_mask_loadu_epi32(tmp,(1 << N) - 1,arr);
}

__m256i __attribute__((noinline)) blend_load(uint32_t * arr) {
    __m256i tmp;
    asm volatile("vpblendd %[m],(%[arr]),%[tmp],%[tmp]\n\t"
                 : [ tmp ] "=x"(tmp)
                 : [ arr ] "r"(arr),[ m ] "i"(((1 << N) - 1))
                 :);
    return tmp;
}


__m256i __attribute__((noinline)) blend_load_epi8(uint32_t * arr) {
    __m256i tmp = _mm256_set1_epi8(uint8_t(0xff));;
    __m256i mask = load_N();
    asm volatile("vpblendvb %[mask],%[tmp]\n\t"
                 : [ tmp ] "+x"(tmp)
                 : [ arr ] "r"(arr),[ mask ] "x"(mask)
                 :);
    return tmp;
}


void __attribute__((noinline)) mask_store(uint32_t * arr,__m256i v) {
    return _mm256_mask_storeu_epi32(arr,v);
}



#define NPAGES (1000)
#define END_OF_PAGE (1024 - N)

#ifndef LOAD_METHOD
#define LOAD_METHOD blend_load
#endif


    
int
main() {
    uint32_t * addr = (uint32_t *)
        mmap(NULL,NPAGES * 4096,PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_PRIVATE,-1,0);

    for(uint32_t i = 0; i < NPAGES; i += 2) {
        mask_store(addr + 1024 * i + END_OF_PAGE,LOAD_METHOD(addr + END_OF_PAGE));
    }
}

Ran: $> perf stat -e page-faults,page-faults ./partial_load

结果与LOAD_METHODblend_loadmask_loadblend_load_epi8相同:

 Performance counter stats for './partial_load':

               548      page-faults                                                 
               548      page-faults                                                 

       0.002155974 seconds time elapsed

       0.000000000 seconds user
       0.002276000 seconds sys

Edit3: 注意是使用clang编译的,它不使用vpblendd来实现_mm256_mask_loadu_epi32

以下是函数的汇编:

0000000000401130 <_Z9mask_loadPj>:
  401130:   b0 1f                   mov    $0x1f,%al
  401132:   c5 fb 92 c8             kmovd  %eax,%k1
  401136:   62 f1 7e a9 6f 07       vmovdqu32 (%rdi),%ymm0{%k1}{z}
  40113c:   c3                      retq   
  40113d:   0f 1f 00                nopl   (%rax)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。