2012-02-16 49 views
8

GCC的vector extensions提供了一个很好的,合理的便携方式访问不同硬件架构上的一些SIMD指令,而不诉诸于hardware specific intrinsics(或自动矢量化)。为GCC的向量扩展加载数据

一个真实的用例,正在计算一个简单的加法校验和。不清楚的一件事是如何安全地将数据加载到矢量中。

typedef char v16qi __attribute__ ((vector_size(16))); 

static uint8_t checksum(uint8_t *buf, size_t size) 
{ 
    assert(size%16 == 0); 
    uint8_t sum = 0; 

    vec16qi vec = {0}; 
    for (size_t i=0; i<(size/16); i++) 
    { 
     // XXX: Yuck! Is there a better way? 
     vec += *((v16qi*) buf+i*16); 
    } 

    // Sum up the vector 
    sum = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5] + vec[6] + vec[7] + vec[8] + vec[9] + vec[10] + vec[11] + vec[12] + vec[13] + vec[14] + vec[15]; 

    return sum; 
} 

铸造的指针向量类型似乎工作,但我很担心,如果SIMD硬件预计矢量类型正确对齐,这可能在一个可怕的方式爆炸。

我想到的唯一的其他选择是使用临时向量并显式加载值(通过memcpy或元素明智的赋值),但是在测试中,这抵消了大部分加速获得的SIMD指令的使用。理想情况下,我会想象这将是一个通用的__builtin_load()函数,但似乎都不存在。

将数据加载到矢量中的更安全方式可能会导致对齐问题?

+2

在GCC x86_64的上对齐的内存运行,这将导致SIGSEGV,当CPU尝试将未对齐的内存加载到SSE寄存器时。 一个合理的选择似乎只是校验和对齐的内存,或者使用正常循环将字节总和,直到第一个16字节边界。 – dcoles 2012-02-17 00:18:39

+0

在您当前的代码中,如果编译器知道输入(但总和不好),加载数据实际上编译得很好:https://godbolt.org/g/DeR3Qv。没有关于输入的知识就不太好:https:// godbolt。组织/克/ LxEkhp – ZachB 2016-09-21 18:14:20

回答

0

你可以使用一个初始化加载的价值观,即做

const vec16qi e = { buf[0], buf[1], ... , buf[15] } 

,并希望GCC变成SSE加载指令这一点。我会用一个反汇编器来验证,但是;-)。此外,为了获得更好的性能,您尝试使对齐的16字节对齐,并通过aligned属性通知该编译器。如果可以保证输入缓冲区将对齐,按字节方式处理它,直到达到16字节的边界。

+0

我不认为调整buf是必要的。这将是,如果我们正在处理指针。 – user1095108 2013-10-15 22:06:08

+0

@ user1095108您希望编译器将其转换为SSE加载指令,该指令相当于'e = * buf'(但由于类型不匹配,您无法这么写)。所以你实际上在这里处理指针。如果编译器可以推断出buf是16字节对齐的,那么它可以使用一个对齐的负载,该负载比pre-ivy-bridge至少快于未对齐的负载。 – fgp 2013-10-16 13:17:45

+0

不,如果您是根据我的经验将“buf”投射到“vec16qi”,那么您会处理指针。 – user1095108 2013-10-16 14:16:45

1

编辑(感谢彼得·科德斯)可以投三分球:

typedef char v16qi __attribute__ ((vector_size (16), aligned (16))); 

v16qi vec = *(v16qi*)&buf[i]; // load 
*(v16qi*)(buf + i) = vec; // store whole vector 

这编译为vmovdqa加载和vmovups存储。如果不知道数据是否对齐,请设置aligned (1)以生成vmovdqu。 (godbolt

注意,也有装载了几个特殊用途的内建和卸载这些寄存器(编辑2):

v16qi vec = _mm_loadu_si128((__m128i*)&buf[i]); // _mm_load_si128 for aligned 
_mm_storeu_si128((__m128i*)&buf[i]), vec); // _mm_store_si128 for aligned 

这似乎是必要使用-flax-vector-conversionschar s到去到v16qi与此功能。

参见:C - How to access elements of vector using GCC SSE vector extension
参见:SSE loading ints into __m128

(提示:最好的短语谷歌是一样的东西“GCC负荷__m128i”)

+1

显然,将未对齐数据加载到GNU C矢量的推荐方法是在声明矢量类型时使用'aligned(1)'属性,并将指针指向该未对齐矢量类型。例如'typedef char __attribute__((vector_size(16),aligned(1)))unaligned_byte16;'。请参阅[我的答案的结尾](http://stackoverflow.com/a/39115055/224132)和Marc Glisse对此的评论。 – 2016-09-21 07:03:29

+0

@PeterCordes谢谢!编辑答案,要简单得多。 – ZachB 2016-09-21 18:41:04

+0

为了提取,我认为你应该使用'vec [0]'。据我了解,矢量类型上的别名标量指针是* not * ok。它适用于'char *',因为'char *'是特殊的,并且允许别名。将'int *'铸造到'v4si *'甚至不会算作别名,因为v4si是用'int'定义的。因为一个额外的属性,英特尔内在函数类型('__m128i')可以别名到其他的东西:'typedef long long __m128i __attribute__((__vector_size__(16),__may_alias __));'没有may_alias, ivec = *(v4si)short_pointer'。我在 – 2016-09-21 19:20:14