GNUC本机向量:如何广播标量,如x86';s_mm_设置1_epi16

GNUC本机向量:如何广播标量,如x86';s_mm_设置1_epi16,c,gcc,clang,simd,intrinsics,C,Gcc,Clang,Simd,Intrinsics,如何编写一个不依赖于x86 set1内在特性的可移植版本 typedef uint16_t v8su __attribute__((vector_size(16))); v8su set1_u16_x86(uint16_t scalar) { return (v8su)_mm_set1_epi16(scalar); // cast needed for gcc } 肯定有比这更好的方法 v8su set1_u16(uint16_t s) { return (v8su){s

如何编写一个不依赖于x86 set1内在特性的可移植版本

typedef uint16_t v8su __attribute__((vector_size(16)));

v8su set1_u16_x86(uint16_t scalar) {
    return (v8su)_mm_set1_epi16(scalar);   // cast needed for gcc
}
肯定有比这更好的方法

v8su set1_u16(uint16_t s) {
    return (v8su){s,s,s,s,  s,s,s,s};
}
我不想写一个AVX2版本的广播单字节

即使是对这一部分的仅gcc或仅clang的回答也会很有趣,因为您希望将变量赋值,而不是仅将其用作二进制运算符的操作数(这在gcc中很有效,请参见下文)


如果我想使用广播标量作为二进制运算符的一个操作数,这适用于gcc(),但不适用于clang:

v8su vecdiv10(v8su v) { return v / 10; }   // doesn't compile with clang
使用clang,如果我只针对x86并且只使用本机向量语法,我可以写:

v8su vecdiv_set1(v8su v) {
    return v / (v8su)_mm_set1_epi16(10);   // gcc needs the cast
}
但是,如果我加宽向量(到
\u mm256\u set1\u epi16
),我必须改变其内在特性,而不是通过在一个地方(对于不需要洗牌的纯垂直SIMD)更改为
向量大小(32)
,将整个代码转换为AVX2。它还破坏了本机向量的部分用途,因为它不会为ARM或任何非x86目标编译

丑陋的铸造是必需的,因为GCC不同于CLAN,不考虑<代码> V8US {AkaY-矢(8)短无符号int } /Cux>兼容<代码>α-M128i {AkaY-矢(2)长long int }//> > 顺便说一句,所有这些都可以通过gcc和clang()编译成好的asm这只是一个如何优雅地编写的问题,可读的语法不会重复标量N次。e、 g.

v/10
非常紧凑,甚至不需要将其放入自己的功能中


使用ICC高效地编译是一个额外的好处,但不是必需的。GNUC本机向量显然是ICC的事后考虑,甚至是
set1_u16
编译为8个标量存储和一个向量加载,而不是MOVD/VPBROADCASTW(启用了
-xHOST
,因为它无法识别
-march=haswell
,但Godbolt在支持AVX2的服务器上运行)。纯粹地强制转换
\u mm\u
内部函数的结果是可以的,但是该部分调用了一个SVML函数

通过两个观察,可以找到GCC和Clang的通用广播解决方案

  • GCC的向量扩展支持标量向量操作
  • x-0=x
    () 这是一个四个浮点向量的解

    #if defined (__clang__)
    typedef float v4sf __attribute__((ext_vector_type(4)));
    #else
    typedef float v4sf __attribute__ ((vector_size (16)));
    #endif
    
    v4sf broadcast4f(float x) {
      return x - (v4sf){};
    }
    

    相同的通用解决方案可用于不同的向量。下面是一个八个无符号短路向量的示例

    #if defined (__clang__)
    typedef unsigned short v8su __attribute__((ext_vector_type(8)));
    #else
    typedef unsigned short v8su __attribute__((vector_size(16)));
    #endif
    
    v8su broadcast8us(short x) {
      return x - (v8su){};
    }
    
    ICC(17)支持GCC向量扩展的一个子集,但不支持
    vector+scalar
    vector*scalar
    ,但广播仍然需要内部函数。MSVC不支持任何向量
    扩展。

    无论如何,你不能合理地在clang中使用gcc向量内部函数,因为它们非常明智地决定实现完全不同的
    \uuuUtin\uShuffle()
    语义。我刚刚发现了一些我编写的旧代码,通过执行
    vectype v={0}来处理丢失的gcc向量内部广播;v+=scalartype。gcc将其优化为广播。它不漂亮(因为它不能是
    const
    ),但它相当短。加零可以很好地用于整数,但不适用于没有
    -ffast math
    的浮点。有符号的零行为(以及可能引发的异常)意味着
    x+0.0
    无法优化到
    x
    ,因此非clang ifdef分支(
    zero+x
    )不会优化:。因为这个答案适用于clang(避免添加到零),所以我认为我还没有准备好接受这个答案。(好吧,对于v8sf,除了在初始值设定项中手动键入同一变量最多8次外,没有其他答案。大概整数类型仍会将
    0+x
    优化为
    x
    (如注释中提到的EOF),因此我们可以将其用于整数向量,避免在v32sc中键入32次)。@PeterCordes,我刚刚为GCC修复了它!您可以执行
    one*x
    ,而不是执行
    zero+x
    。看看我更新的答案。很好,这正是回答这个问题的窍门。你应该把这个版本作为你答案的第一个版本,因为它没有坏处。您可以通过执行
    v4sf one=((v4sf){})+1.0初始化
    one
    ,而无需键入逗号分隔的列表。0.0+1.0不会在编译时进行优化,因为两个操作数都是常量。这可以消除gcc与clang
    #ifdef
    的区别,而不是在
    typedef
    中,但ICC似乎仍然需要自定义
    \u mm\u set1.
    内部函数。@PeterCordes。一个更简单的解决方案
    x-(v4sf){}
    。看见显然
    x-0
    也可以。这就是问题所在。