使用GCC向量扩展存储、修改和检索字符串?
提供了SIMD指令的抽象 我想知道如何将它们用于字符串处理,例如屏蔽缓冲区的每个字节:使用GCC向量扩展存储、修改和检索字符串?,c,gcc,vectorization,simd,C,Gcc,Vectorization,Simd,提供了SIMD指令的抽象 我想知道如何将它们用于字符串处理,例如屏蔽缓冲区的每个字节: typedef uint8_t v32ui __attribute__ ((vector_size(32))); void f(const uint8_t *begin, const uint8_t *end, uint8_t *o) { for (; begin < end; begin += 32, o+=32) *(v32ui*) o = (*(v32ui*) begin)
typedef uint8_t v32ui __attribute__ ((vector_size(32)));
void f(const uint8_t *begin, const uint8_t *end, uint8_t *o)
{
for (; begin < end; begin += 32, o+=32)
*(v32ui*) o = (*(v32ui*) begin) & 0x0fu;
}
typedef uint8_t v32ui_uuu属性(向量大小(32));
空f(常数uint8_*begin、常数uint8_*end、常数uint8_*o)
{
for(;begin
假设输入和输出缓冲区正确对齐(32字节),GCC verctor扩展是否支持并定义了这种转换
这是对字符串使用向量扩展最有效的方法吗
或者我必须显式地将字符串的部分存储/检索到向量中吗
例如:
void f(const uint8_t *begin, const uint8_t *end, uint8_t *o)
{
for (; begin < end; begin += 32, o+=32) {
v32ui t;
memcpy(&t, begin, 32);
t &= 0f0u;
memcpy(o, &t, 32);
}
}
void f(const uint8\u t*begin、const uint8\u t*end、uint8\u t*o)
{
for(;begin
还是有比memcpy
更好/更有效的方法
当假设输入或输出缓冲区(或两者)未对齐时,如何安全/有效地使用向量扩展进行字符串处理?向量需要在寄存器中处理,因此
memcpy
在这里不可能有用
如果自动矢量化不能生成好的代码,标准的技术是使用矢量内部函数。如果您可以使用能够在多个体系结构上编译为SIMD指令的ops实现所需的功能,那么gcc向量语法可能是一种很好的方法
我试用了GCC4.9.2的第一个版本。它生成的正是您所希望的,64位AVX。(256位加载、矢量和存储)
不使用-march
或任何东西,只使用基线amd64(SSE2),它将输入复制到堆栈上的缓冲区,并从那里加载。我认为这是在输入/输出缓冲区未对齐的情况下进行的,而不仅仅是使用movdqu
。不管怎么说,这是一个非常糟糕的慢代码,在GP寄存器中一次处理8个字节要比这个废话快得多
gcc-march=native-O3-sv32ui_和.c
(在Sandybridge上(没有AVX2的AVX)):
请注意,缺少标量清理或未对齐数据的处理<当地址对齐时,code>vmovdqu与vmovdqa
一样快,所以不使用它有点傻
gcc-O3-sv32ui_和.c
很奇怪
.globl f
f:
.LFB0:
cmpq %rsi, %rdi
movdqa .LC0(%rip), %xmm0 # load a vector of 0x0f bytes
jnb .L9
leaq 8(%rsp), %r10
andq $-32, %rsp
pushq -8(%r10)
pushq %rbp
movq %rsp, %rbp
pushq %r10
.p2align 4,,10
.p2align 3
.L5:
movq (%rdi), %rax
addq $32, %rdi
addq $32, %rdx
movq %rax, -80(%rbp)
movq -24(%rdi), %rax
movq %rax, -72(%rbp)
movq -16(%rdi), %rax
movdqa -80(%rbp), %xmm1
movq %rax, -64(%rbp)
movq -8(%rdi), %rax
pand %xmm0, %xmm1
movq %rax, -56(%rbp)
movdqa -64(%rbp), %xmm2
pand %xmm0, %xmm2
movaps %xmm1, -112(%rbp)
movq -112(%rbp), %rcx
movaps %xmm2, -96(%rbp)
movq -96(%rbp), %rax
movq %rcx, -32(%rdx)
movq -104(%rbp), %rcx
movq %rax, -16(%rdx)
movq -88(%rbp), %rax
movq %rcx, -24(%rdx)
movq %rax, -8(%rdx)
cmpq %rdi, %rsi
ja .L5
popq %r10
popq %rbp
leaq -8(%r10), %rsp
.L9:
rep ret
所以我想,如果gcc向量扩展有时会生成如此糟糕的代码,那么就不能安全地使用它。对于intrinsic,最简单的实现是:
#include <immintrin.h>
#include <stdint.h>
void f(const uint8_t *begin, const uint8_t *end, uint8_t *o)
{
__m256i mask = _mm256_set1_epi8(0x0f);
for (; begin < end; begin += 32, o+=32) {
__m256i s = _mm256_loadu_si256((__m256i*)begin);
__m256i d = _mm256_and_si256(s, mask);
_mm256_storeu_si256( (__m256i*)o, d);
}
}
#包括
#包括
空f(常数uint8_*begin、常数uint8_*end、常数uint8_*o)
{
__m256i掩码=_mm256_set1_epi8(0x0f);
for(;begin
这将生成与gcc矢量版本(使用AVX2编译)相同的代码。注意:这使用的是VPAND
,而不是VANDPS
,因此需要AVX2
对于大型缓冲区,在输入或输出缓冲区与16或32字节对齐、向量循环以及所需的任何标量清理之前,进行标量启动是值得的。对于较小的缓冲区,最好只使用未对齐的加载/存储和末尾的简单标量清理
由于您特别询问了字符串,如果字符串以nul结尾(隐式长度),则在跨越页面边界时必须小心,如果字符串在页面结尾之前结束,但您的读取跨越了边界,则不会出错。对齐的大小写应该可以,但是你有没有检查gcc的矢量器在没有显式使用矢量的情况下是否还没有生成合适的代码?@marglisse,上面的屏蔽只是一个玩具示例。实际上,我想对字符串缓冲区中每个向量大小的增量应用一系列操作(字节移位、字节洗牌等)。类似问题:
#include <immintrin.h>
#include <stdint.h>
void f(const uint8_t *begin, const uint8_t *end, uint8_t *o)
{
__m256i mask = _mm256_set1_epi8(0x0f);
for (; begin < end; begin += 32, o+=32) {
__m256i s = _mm256_loadu_si256((__m256i*)begin);
__m256i d = _mm256_and_si256(s, mask);
_mm256_storeu_si256( (__m256i*)o, d);
}
}