是否应为每个操作传递或创建SSE数据类型?

时间:2012-07-21 22:38:16

标签: c++ sse

我正在尝试制作自己的C ++矢量数学库,我有兴趣用SSE优化它。对于我的vec2和vec3数据类型,我不能直接存储__m128类型,因为它们必须是它们的预期大小,但是vec4呢?假设我的vec4类型看起来像这样(忽略16字节对齐要求以简化讨论):

union vec4 {
  struct {float x, y, z, w;};
  __m128 sse;
}

vec4 operator+(const vec4& left, const vec4& right) {
  vec4 result;
  result.sse = _mm_add_ps(left.sse, right.sse);
  return result;
}

这是建议的方法吗?还是有一些重要的理由不让我想不到?即,我应该这样做:

struct vec4 {
  float x, y, z, w;
};

vec4 operator+(const vec4& left, const vec4& right) {
  __m128 leftSSE = _mm_load_ps(reinterpret_cast<const float*>(&left));
  __m128 rightSSE = _mm_load_ps(reinterpret_cast<const float*>(&right));
  __m128 resultSSE = _mm_add_ps(leftSSE, rightSSE);
  vec4 result;
  _mm_store_ps(reinterpret_cast<float*>(&result), resultSSE);
  return result;
}

虽然我们正在研究它,但我的理论vec2和vec3类型呢?首先将它们转换为vec4然后使用SIMD指令或单独处理它们的标量元素会更快吗?

2 个答案:

答案 0 :(得分:6)

你应该避免像瘟疫这样的第二个版本,因为如果所有的小/原始操作都有加载/存储指令,那么使用这些操作的整体表达式将与加载/存储指令的开销相比相形见绌并完全超过实际工作要做。

所有向量操作/函数的编写方式应假设并仅强制执行已加载到sse寄存器中的参数,并仅处理这些参数。加载/存储操作应该明确地写在那些控制的函数的上下文之外,这样你只需要在循环的每次迭代或非常偶然的情况下执行一次。

Mystical试图指出的是,当您访问SSE内部类型的单个元素时,这些元素会导致生成加载/存储指令,因此您应该避免访问/修改单个元素。注意生成的组件。

对于vec2 / 3,我只需要为vec4创建强类型别名,并在首次创建时将其他组件清零。 SSE还有大多数操作的变体,它们仅适用于第一个组件,因此这是值得记住的另一件事。

要从SSE中获得最大的吞吐量,您需要处理SoA,混合SoA-AoS,或者随时随地进行SoA形式的随机播放。

查看this视频。

答案 1 :(得分:0)

在visual C ++中(我假设您正在使用 - 如果您正在使用其他内容,请指定)__m128defined,如下所示:

typedef struct __declspec(intrin_type) __declspec(align(16)) __m128 {
   float m128_f32[4];
} __m128;

它的表示应该与四个浮点数的结构相同,除了它是128位对齐的 - 它也应该被转移到你的联合中。特别是,这意味着您的第二个示例不正确,除非您使用_mm_loadu_ps,因为结构可能未对齐。

因此,以这种方式在联合中使用__m128有助于确保您的结构与快速对齐的加载正确对齐,因此这不是一个坏主意。