我正在尝试制作自己的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;
}
Run Code Online (Sandbox Code Playgroud)
这是建议的方式,还是有一些很大的理由不让我想不到?即,我应该这样做:
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;
}
Run Code Online (Sandbox Code Playgroud)
虽然我们在这里,我的理论vec2和vec3类型怎么样?首先将它们转换为vec4然后使用SIMD指令或单独处理它们的标量元素会更快吗?
你应该避免像瘟疫这样的第二个版本,因为如果所有的小/原始操作都有加载/存储指令,使用这些操作的整体表达式将与加载/存储指令的开销相比相形见绌,并且完全超过实际的工作量.完成.
所有向量操作/函数都应该以一种方式编写,该方式假定并仅强制执行已加载到sse寄存器中的参数,并仅处理这些参数.加载/存储操作应该明确地写在那些控制的函数的上下文之外,这样你只需要在循环的每次迭代或非常不频繁地执行一次.
此外,Mystical试图指出的是,当您访问SSE内部类型的单个元素时,这些元素会导致生成加载/存储指令,因此您应该再次避免访问/修改单个元素.注意生成的组件.
对于vec2/3,我只需要为vec4创建强类型别名,并在首次创建时将其他组件清零.SSE还有大多数操作的变体,它们仅适用于第一个组件,因此这是值得记住的另一件事.
为了从SSE中获得最大的吞吐量,您需要处理SoA,混合SoA-AoS或者即时调配/混洗到SoA形式.
看看这个视频.