c-强制编译器使用Intrnsics中的内存操作数



是否存在强制C编译器直接使用内存操作数的语法?

在好的旧asm时间里,我们只需在指令中写入操作数的位置——"实数"寄存器或内存指针(由地址指向的位置(。

但是,在C的内部伪asm中,我看不到强制编译器在指令中使用内存指针的方法(拒绝将数据从内存(缓存(加载到"寄存器",即将寄存器文件加载的内容丢弃到缓存,并导致重新加载(惩罚(。

我知道程序员很容易简单地将"变量"操作数写入Instinc,并让编译器决定是先从内存加载还是直接使用(如果可能的话(。

当前任务:我想在AVX2 CPU上用512字节的寄存器文件(每个32字节的16个ymm"寄存器"(计算8x8 8位块序列的SAD。因此,它可以加载8个8x8 8位源块,以完全填充可用的AVX2寄存器文件。

我想在所有寄存器文件中加载源块,并针对这些源块和每个ref位置从内存中测试不同的"ref"位置一次。所以我想防止CPU将ref块从缓存加载到寄存器文件,并在sad指令中使用"内存操作数"。

使用asm,我们只需编写类似的内容

(load all 16 ymm registers with src)
vpsadbw ymm0, ymm0, [ref_base_address_register + some_offset...]

但在具有内在特征的C文本中,它是

__m256i src = load_src(src_pointer);
__m256i ref = load_ref(ref_pointer); 
__m256i sad_result= _mm256_sad_epu8(src, ref)

它没有办法指向编译器使用像这样的有效内存操作数

__m256i src = load_src(src_pointer);
__m256i sad_result= _mm256_sad_epu8(src, *ref_pointer)

或者取决于"任务大小",如果编译器将耗尽可用寄存器,它将自动切换到内存操作数版本,程序员可以编写

__m256i sad_result=_mm256_sad_epu8(*(__m256i*)src_pointer, *(__m256i*)ref_pointer)

并且期望编译器将2个操作数中的一个加载到寄存器文件并使用内存中的下一个?

没有,除了一些特定的内部函数有指针操作数,即使它们不是纯加载或纯存储1

内部函数的部分目的是抽象掉寄存器分配的细节,就像它对intdouble所做的那样,所以当这是一件好事时,由编译器将内容保留在寄存器中。这种情况通常会发生,因此,如果您担心优化器无法将内部加载折叠到内存源操作数中,请检查asm输出(例如https://godbolt.org/或本地(。AVX(VEX编码(允许折叠甚至未对齐的负载,因为与传统SSE不同,默认情况下不需要对齐。

当编译器失败时,这可能会很糟糕,就像许多用于_mm256_cvtepu8_epi32( _mm_loadl_epi64(p) )-GCC的编译器一样,后者用于发出实际的movq加载和reg regvpmovzxbd。只有在GCC9和更高版本中,我们才能获得内存源vpmovzxbd。(将8个字符从内存加载到__m256变量中,作为压缩的单精度浮点(

或者,对于您的情况,如果编译器泄露了错误的内容,唯一的解决方法是提交一份遗漏的优化错误报告,然后等待新的编译器版本。或者用asm(内联或独立(编写一个版本。


内部函数模型的设计者还希望提供load/loadustore/storeu内部函数来向编译器传递对齐信息。(对于float/double,在float*__m128*之间进行强制转换或其他任何操作。(如果编译器不能看穿数组并将其保存在寄存器中,则_mm_load_si128((__m128i*)foo)*(__m128i*)foo完全相同,与访问__m128i数组的元素几乎相同。请参阅硬件SIMD矢量指针和相应类型之间的"interpret_cast"是未定义的行为吗?

令人困惑的是,加载内部函数看起来像asm-loads/store,但当启用优化时,它们实际上根本不同。


脚注1:AVX-512有一些特殊的指令,它们具有相应有趣的内部函数,如VPMOVDB mem128 {k}, zmm2-void _mm512_mask_cvtepi32_storeu_epi8(void * d, __mmask16 k, __m512i a);。能够存储到内存为Xeon Phi(Knight’s Landing(提供了一种在没有AVX-512BW的情况下为vmovdqu8进行字节屏蔽存储的方法。

最新更新