GCC SSE 手写与生成
GCC SSE Handwritten vs. Generated
我正在研究 SIMD 优化并编写了一个 3 个非常简单的向量 类,并以 2 种不同的方式实现加法,一种是手写组件,一种是使用 _mm_add_ps https://godbolt.org/z/fPAERV。
有趣的是,GCC 无法(或者我没有正确地告诉它 x))使用 SSE 实现 vector2 的加法,只有在明确地将第四个浮点数添加到向量之后(就像在 vector3 中一样)gcc 使用 SEE 指令生成加法,即使我将矢量对齐到 16 字节的边界上。谁能告诉我为什么?
#include <xmmintrin.h>
struct alignas(16) vector final {
union {
struct {
float x, y, z;
};
float axes[3];
__m128 v;
};
vector(float x, float y, float z) noexcept : x(x), y(y), z(z) {};
vector(__m128 v) noexcept : v(v){};
};
vector operator+(const vector& v0, const vector& v1) noexcept {
return {_mm_add_ps(v0.v, v1.v)};
}
struct alignas(16) vector2 final {
union {
struct {
float x, y, z;
};
float axes[3];
__m128 v;
};
vector2(float x, float y, float z) noexcept : x(x), y(y), z(z) {};
vector2(__m128 v) noexcept : v(v){};
};
vector2 operator+(const vector2& v0, const vector2& v1) noexcept {
return {v0.x + v1.x, v0.y + v1.y, v0.z + v1.z};
}
struct alignas(16) vector3 final {
union {
struct {
float x, y, z, w;
};
float axes[4];
__m128 v;
};
vector3(float x, float y, float z, float w) noexcept : x(x), y(y), z(z), w(w) {};
vector3(__m128 v) noexcept : v(v){};
};
vector3 operator+(const vector3& v0, const vector3& v1) noexcept {
return {v0.x + v1.x, v0.y + v1.y, v0.z + v1.z, v0.w + v1.w};
}
使用 gcc9.2 和 -std=c++17 -O3 -Wall -Wextra 生成程序集
operator+(vector const&, vector const&):
movaps xmm1, XMMWORD PTR [rsi]
addps xmm1, XMMWORD PTR [rdi]
movdqa xmm0, xmm1
movaps XMMWORD PTR [rsp-24], xmm1
movq xmm1, QWORD PTR [rsp-16]
ret
operator+(vector2 const&, vector2 const&):
movss xmm1, DWORD PTR [rdi+4]
movss xmm0, DWORD PTR [rdi+8]
addss xmm1, DWORD PTR [rsi+4]
addss xmm0, DWORD PTR [rsi+8]
movss xmm2, DWORD PTR [rdi]
addss xmm2, DWORD PTR [rsi]
movss DWORD PTR [rsp-20], xmm1
movss DWORD PTR [rsp-16], xmm0
movq xmm1, QWORD PTR [rsp-16]
movss DWORD PTR [rsp-24], xmm2
movq xmm0, QWORD PTR [rsp-24]
ret
operator+(vector3 const&, vector3 const&):
movaps xmm0, XMMWORD PTR [rdi]
addps xmm0, XMMWORD PTR [rsi]
movaps XMMWORD PTR [rsp-40], xmm0
mov rax, QWORD PTR [rsp-32]
movq xmm0, QWORD PTR [rsp-40]
movq xmm1, rax
mov QWORD PTR [rsp-16], rax
ret
"inventing writes" 通常是不允许的,并且会产生令人讨厌的编译器错误。 (因为线程安全,例如踩到另一个线程的写入)。
即使它是联合对象的一部分,GCC 内部可能将最后一个元素视为单独的元素,并且不愿意用 "garbage" 来编写它。所以是的,这是一个错过的优化,您必须手动解决。
一般来说,SIMD 向量不太适合保存 3D 几何向量。理想情况下,您可以构建数据,这样您就可以拥有四个 x
坐标的 __m128 x
,以及四个 y
坐标的另一个 __m128 y
,等等。然后您可以进行 4 个向量加法在 3 addps
条指令中。更好的是,同时执行 4 个向量长度或使用来自同一向量的 x、y 和 z 的其他操作不涉及任何改组。
请参阅 https://whosebug.com/tags/sse/info for links, especially Slides: SIMD at Insomniac Games (GDC 2015),其中详细介绍了如何有效使用 SIMD 等。
但是可以肯定的是,如果您已经针对可以以不同方式布置数据的情况这样做了,那么在其他情况下您可能只有几个单独的向量并且需要 "float3" 布局,并且仍然可以使用 SIMD 来加快速度。
我正在研究 SIMD 优化并编写了一个 3 个非常简单的向量 类,并以 2 种不同的方式实现加法,一种是手写组件,一种是使用 _mm_add_ps https://godbolt.org/z/fPAERV。 有趣的是,GCC 无法(或者我没有正确地告诉它 x))使用 SSE 实现 vector2 的加法,只有在明确地将第四个浮点数添加到向量之后(就像在 vector3 中一样)gcc 使用 SEE 指令生成加法,即使我将矢量对齐到 16 字节的边界上。谁能告诉我为什么?
#include <xmmintrin.h>
struct alignas(16) vector final {
union {
struct {
float x, y, z;
};
float axes[3];
__m128 v;
};
vector(float x, float y, float z) noexcept : x(x), y(y), z(z) {};
vector(__m128 v) noexcept : v(v){};
};
vector operator+(const vector& v0, const vector& v1) noexcept {
return {_mm_add_ps(v0.v, v1.v)};
}
struct alignas(16) vector2 final {
union {
struct {
float x, y, z;
};
float axes[3];
__m128 v;
};
vector2(float x, float y, float z) noexcept : x(x), y(y), z(z) {};
vector2(__m128 v) noexcept : v(v){};
};
vector2 operator+(const vector2& v0, const vector2& v1) noexcept {
return {v0.x + v1.x, v0.y + v1.y, v0.z + v1.z};
}
struct alignas(16) vector3 final {
union {
struct {
float x, y, z, w;
};
float axes[4];
__m128 v;
};
vector3(float x, float y, float z, float w) noexcept : x(x), y(y), z(z), w(w) {};
vector3(__m128 v) noexcept : v(v){};
};
vector3 operator+(const vector3& v0, const vector3& v1) noexcept {
return {v0.x + v1.x, v0.y + v1.y, v0.z + v1.z, v0.w + v1.w};
}
使用 gcc9.2 和 -std=c++17 -O3 -Wall -Wextra 生成程序集
operator+(vector const&, vector const&):
movaps xmm1, XMMWORD PTR [rsi]
addps xmm1, XMMWORD PTR [rdi]
movdqa xmm0, xmm1
movaps XMMWORD PTR [rsp-24], xmm1
movq xmm1, QWORD PTR [rsp-16]
ret
operator+(vector2 const&, vector2 const&):
movss xmm1, DWORD PTR [rdi+4]
movss xmm0, DWORD PTR [rdi+8]
addss xmm1, DWORD PTR [rsi+4]
addss xmm0, DWORD PTR [rsi+8]
movss xmm2, DWORD PTR [rdi]
addss xmm2, DWORD PTR [rsi]
movss DWORD PTR [rsp-20], xmm1
movss DWORD PTR [rsp-16], xmm0
movq xmm1, QWORD PTR [rsp-16]
movss DWORD PTR [rsp-24], xmm2
movq xmm0, QWORD PTR [rsp-24]
ret
operator+(vector3 const&, vector3 const&):
movaps xmm0, XMMWORD PTR [rdi]
addps xmm0, XMMWORD PTR [rsi]
movaps XMMWORD PTR [rsp-40], xmm0
mov rax, QWORD PTR [rsp-32]
movq xmm0, QWORD PTR [rsp-40]
movq xmm1, rax
mov QWORD PTR [rsp-16], rax
ret
"inventing writes" 通常是不允许的,并且会产生令人讨厌的编译器错误。 (因为线程安全,例如踩到另一个线程的写入)。
即使它是联合对象的一部分,GCC 内部可能将最后一个元素视为单独的元素,并且不愿意用 "garbage" 来编写它。所以是的,这是一个错过的优化,您必须手动解决。
一般来说,SIMD 向量不太适合保存 3D 几何向量。理想情况下,您可以构建数据,这样您就可以拥有四个 x
坐标的 __m128 x
,以及四个 y
坐标的另一个 __m128 y
,等等。然后您可以进行 4 个向量加法在 3 addps
条指令中。更好的是,同时执行 4 个向量长度或使用来自同一向量的 x、y 和 z 的其他操作不涉及任何改组。
请参阅 https://whosebug.com/tags/sse/info for links, especially Slides: SIMD at Insomniac Games (GDC 2015),其中详细介绍了如何有效使用 SIMD 等。
但是可以肯定的是,如果您已经针对可以以不同方式布置数据的情况这样做了,那么在其他情况下您可能只有几个单独的向量并且需要 "float3" 布局,并且仍然可以使用 SIMD 来加快速度。