5

我目前正在尝试制作自己的 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 指令还是单独处理它们的标量元素会更快吗?

4

2 回答 2

6

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

您的所有向量操作/函数都应该以一种假设并强制执行已加载到 sse 寄存器中的参数的方式编写,并且只处理这些参数。加载/存储操作应该在这些函数的上下文之外显式编写,这样您只需要在每次循环迭代时执行一次或非常不频繁地执行此操作。

另外,Mystical 试图指出的是,当您访问 SSE 内在类型的单个元素时,会导致生成加载/存储指令,因此您应该再次避免访问/修改单个元素。注意生成的程序集。

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

为了从 SSE 中获得最大的吞吐量,您需要处理 SoA、混合 SoA-AoS 或在运行中进行混编/改组为 SoA 形式。

看看这个视频。

于 2012-07-23T20:54:52.897 回答
0

在 Visual C++(我假设您正在使用 - 如果您正在使用其他内容,请指定)__m128定义如下:

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

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

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

于 2012-07-21T22:47:21.710 回答