【问题标题】:Get member of __m128 by index?通过索引获取 __m128 的成员?
【发布时间】:2012-09-19 10:06:24
【问题描述】:

我有一些代码,最初是由与 MSVC 合作的人给我的,我正试图让它在 Clang 上工作。这是我遇到问题的功能:

float vectorGetByIndex( __m128 V, unsigned int i )
{
    assert( i <= 3 );
    return V.m128_f32[i];
}

我得到的错误如下:

Member reference has base type '__m128' is not a structure or union.

我环顾四周,发现 Clang(可能还有 GCC)在将 __m128 视为结构或联合时存在问题。但是,我还没有找到关于如何取回这些值的直接答案。我尝试过使用下标运算符,但无法做到这一点,而且我浏览了大量 SSE 内在函数,但还没有找到合适的。

【问题讨论】:

  • 知道原来的内部接口故意遗漏了这个功能是很有用的,因为在硬件中没有有效的方法来有效地做到这一点。编译器(如 MSVC)将提供扩展(如 m128_f32)来执行此操作。但这只是掩盖了性能问题。
  • 是的,我知道它完全消除了一次处理向量的好处——我认为这个特殊的功能不适合大量使用。由于我正在移植,我想避免留下任何未实现的东西。
  • @Mysticial 在 gcc 上使用 -mfpmath=sse 时(以及在 msvc 上生成 64 位代码时),浮点值在 sse 寄存器中。您可以使用_mm_cvtss_f32(V) 和其他元素有效地返回向量的最低有效部分,方法是首先将所需的值改组到低元素中。
  • 值得注意的是,对于较新的 clang 版本,您可以使用 return V[i]
  • @benwad 备注 - 很明显,[i]V.m128_f32 之后应该有一个 V.m128_f32 - 因为你说这适用于 MSVC。而且这种更改显然不会影响 clang 错误消息,而且细节对您的要求并不重要。我已尝试两次将其作为对问题的编辑提交,但大多数审阅者认为我正在改变问题的意图,因此没有发生。

标签: c++ clang sse simd intrinsics


【解决方案1】:

作为对 hirschhornsalz 解决方案的修改,如果 i 是编译时常量,则可以使用 shuffle 完全避免联合路径:

template<unsigned i>
float vectorGetByIndex( __m128 V)
{
    // shuffle V so that the element that you want is moved to the least-
    // significant element of the vector (V[0])
    V = _mm_shuffle_ps(V, V, _MM_SHUFFLE(i, i, i, i));
    // return the value in V[0]
    return _mm_cvtss_f32(V);
}

一个标量浮点数只是XMM寄存器的底部元素,高元素允许非零; _mm_cvtss_f32 是免费的,将编译为零指令。这将作为一个 shufps 内联(或者对于 i==0 什么都没有)。

编译器足够聪明,可以优化 i==0 的随机播放(长期过时的 ICC13 除外),因此不需要 if (i)https://godbolt.org/z/K154Pe。 clang 的 shuffle 优化器会将 vectorGetByIndex&lt;2&gt; 编译为比 shufps 短 1 个字节的 movhlps xmm0, xmm0 并产生相同的低元素。对于其他编译器,您可以使用 switch/case 手动执行此操作,因为 i 是编译时常量,但是在手动矢量化时使用它的少数地方的 1 字节代码大小非常简单。


请注意,SSE4.1 _mm_extract_epi32(V, i); 在这里不是一个有用的 shuffle:extractps r/m32, xmm, imm 只能将 FP 位模式提取到 integer 寄存器或内存 (https://www.felixcloutier.com/x86/extractps)。 (并且内在函数将其作为int 返回,因此它实际上会编译为extractps + cvtsi2ss 以在FP 位模式上进行int->float 转换,除非您在C++ 代码中键入它。但是你会期望它编译成 extractps eax, xmm0, i / movd xmm0, eax 这对于 shufps 来说是很糟糕的。)

extractps 唯一有用的情况是编译器希望将此结果直接存储到内存中,并将存储折叠到提取指令中。 (对于 i!=0,否则它将使用 movss)。要将结果作为标量浮点数保留在 XMM 寄存器中,shufps 很好。

SSE4.1 insertps 可以使用但没必要:它可以在获取任意源元素的同时将其他元素归零。)

【讨论】:

  • 这应该使用_mm_cvtss_f32而不是_mm_store_ss
  • @Dan:好点;当我写答案时,我错过了这个内在。当然,它使它更紧凑。
  • _mm_extract_epi32 将 FP 位模式提取为整数(在 GP 寄存器中!),然后将该整数转换为 float_mm_extract_ps (SSE4.1 extractps) 做同样的事情。去掉__SSE4_1__这个部分就很好了。
【解决方案2】:

参加晚会但发现这在 MSVC 中对我有用,其中 z 是 __m128 类型的变量。

#define _mm_extract_f32(v, i)       _mm_cvtss_f32(_mm_shuffle_ps(v, v, i))

__m128 z = _mm_setr_ps(1.0, 2.0, 3.0, 4.0);

float f = _mm_extract_f32(z, 2);

或者更简单

__m128 z;

float f = z.m128_f32[2];  // to get the 3rd float value in the vector

【讨论】:

  • 是的,MSVC 将__m128 类型定义为各种元素大小的联合。这对于 GCC 或 clang 等其他编译器是完全不可移植的,所以我建议不要使用它,除非你将它包装在一个辅助函数中,你可以稍后 #ifdef 以实现可移植性。即不要在整个代码中分散.m128_f32 访问权限。定义 __m128float[4] 的联合将适用于 MSVC 和一些(但不是全部)其他编译器,请参阅 @Jordan 的答案和 cmets。或者只是 _mm_store_ps 到一个 tmp 数组并重新加载,让编译器根据需要将其优化为随机播放。
【解决方案3】:

使用

template<unsigned i>
float vectorGetByIndex( __m128 V) {
    union {
        __m128 v;    
        float a[4];  
    } converter;
    converter.v = V;
    return converter.a[i];
}

不管可用的指令集如何,它都可以工作。

注意:即使 SSE4.1 可用且 i 是编译时间常数,您不能以这种方式使用 pextract 等,因为这些指令提取 32 位整数,不是float

// broken code starts here
template<unsigned i>
float vectorGetByIndex( __m128 V) {
    return _mm_extract_epi32(V, i);
}
// broken code ends here

我没有删除它,因为它是一个有用的提醒如何不做的事情。

【讨论】:

  • 这是错误的。 1)_mm_extract_epi32 将 __m128i 作为其第一个参数,您正在传递 __m128 - 代码不会编译。 2) 如果你用_mm_castps_si128 解决了这个问题,_mm_extract_epi32 在通用寄存器(例如 eax)中将原始浮点值作为整数返回。 3) 该值将是 int 转换为浮点数:1.0f == 0x3F800000 = 1,065,353,216。对于1.0f,您的代码将在 SSE4 上返回 1.06535e+09。 4)即使你通过强制转换和重新解释取消引用来解决这个问题,它也是低效的。您的代码使用了错误的内在函数。使用_mm_shuffle_ps_mm_cvtss_f32
  • @doug65536 这段代码可以正常工作和编译。你使用 MSVC 吗?
  • 不,我使用 gcc,我运行 linux。我什至测试了它,您的代码无法编译。您可能没有将 -msse4 传递给 gcc,因此 #ifdef 始终为假。 I tested it here 我必须将 _mm_extract_epi32 更改为 _mm_extract_ps 才能编译。 Extract 使用 movd 指令将值放入通用(整数)寄存器。
  • 你是对的,_mm_extract_epi32 对浮点数不起作用——我从一个类似的整数例程中获取了代码,没有进一步检查#ifdef __SSE4_1__ 的情况。谢谢你的发言。
  • 不用担心,SSE 内在函数非常容易出错。我们都会犯这些错误。
【解决方案4】:

我的使用方式是

union vec { __m128 sse, float f[4] };

float accessmember(__m128 v, int index)
{
    vec v.sse = v;
    return v.f[index];
}

对我来说似乎效果很好。

【讨论】:

  • 联合在 C++ 中没有很好的定义;见Accessing inactive union member and undefined behavior?。 GCC 和 SunCC 有时会产生不好的结果。 Aarch64 下的 GCC 和 x86_64 上的 SunCC。
  • @jww:GNU C++ 确实在 C++ 中定义了联合类型双关语的行为以匹配 C99。如果 GCC 失败,则可能是编译器错误或由于程序中其他地方的 other 未定义行为。 gcc.gnu.org/onlinedocs/gcc/…。 (适用于 GNU C++,根据“一些选择记录在 C 语言的相应文档中。请参阅 C 实现。”gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Implementation.html 中的措辞。
  • 当然这只是 GNU C++,而不是 ISO C++,并且在__m128 的 GNU / clang 实现中,您可以简单地索引 __m128 本身,例如 v[index],因为它被定义为一个 GNU C 原生向量,例如 typedef float __m128 __attribute((vector_size(16),may_alias));。当然,union hack 可以移植到其他一些 C++ 编译器,例如 MSVC,它们以不同的方式定义 __m128。所以它可能很有用,但需要注意的是,它并不是严格可移植到所有支持 Intel 内在函数的 C++ 实现。
【解决方案5】:

联合可能是最便携的方式:

union {
    __m128 v;    // SSE 4 x float vector
    float a[4];  // scalar array of 4 floats
} U;

float vectorGetByIndex(__m128 V, unsigned int i)
{
    U u;

    assert(i <= 3);
    u.v = V;
    return u.a[i];
}

【讨论】:

  • MSDN 说你不应该这样做,通过load and set 运营商留下访问权限。
  • @Steve-o:gcc 另有说法:gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/…
  • 我看到很多关于这种方法的矛盾意见,但它看起来是 Mac 上唯一可行的选择。谢谢!
  • @hirschhornsalz 我不清楚u.a[i] 是否真的适合您链接中的第一个“支持”示例,而不是第二个“可能不起作用”示例——因为u.a 产生一个@ 987654327@ 然后取消引用由[i] 单独完成。我自己使用这种技术,但我发现似乎没有“官方”方式来做这种类型的双关语,这很烦人(即使这是 GCC 所说的可行的方法;语言不能保证)。是否有必要将一个结构实际 memcpy 到另一个结构?
  • 我还要补充一点,gcc 经常忽略使用 union 进行类型双关,因此不会完成隐含的存储和加载 - 在 union 的两个视图都适合的情况下一个寄存器。因此,这使其成为许多类型双关语的最有效技术(除了最安全之外)。但在这个特定的例子中,这不会发生,因为没有操作码可以从 sse 寄存器中提取元素 [i]。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-29
  • 1970-01-01
  • 1970-01-01
  • 2013-03-05
  • 2012-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多