我一直在尝试使用不同的编译器,以查看它们在没有难看的内在函数或手写asm的情况下可用于编写有效的SIMD代码(aarch64和x64)的程度。到目前为止,只有Clang似乎对aarch64做得正确。我想我可能缺少一些编译器提示或编译标志。
我尝试使用__restrict
关键字,并且在GCC和Clang上使用了以下编译标志:
-force-vector-width=8 -ffast-math -ftree-vectorize
代码如下:
typedef union
{
float coords[3];
struct { float x; float y; float z; };
} vec3; // simple 3D vector
vec3 AddVectors(vec3 a, vec3 b)
{
vec3 x = { { a.x + b.x, a.y + b.y, a.z + b.z } };
return x;
}
void Add4Vectors(vec3* __restrict a, vec3* __restrict b)
{
a[0] = AddVectors(a[0], b[0]);
a[1] = AddVectors(a[1], b[1]);
a[2] = AddVectors(a[2], b[2]);
a[3] = AddVectors(a[3], b[3]);
}
void Add16Vectors(vec3* __restrict a, vec3* __restrict b)
{
Add4Vectors(a, b);
Add4Vectors(a + 4, b + 4);
Add4Vectors(a + 8, b + 8);
Add4Vectors(a + 12, b + 12);
}
void Add64Vectors(vec3* __restrict a, vec3* __restrict b)
{
Add16Vectors(a, b);
Add16Vectors(a + 16, b + 16);
Add16Vectors(a + 32, b + 32);
Add16Vectors(a + 48, b + 48);
}
您可以在GodBolt.org上玩 https://godbolt.org/z/SW3aH-
这是我从c 8中获得的结果(标志:-O3 -target aarch64)
AddVectors(vec3, vec3): // @AddVectors(vec3, vec3)
fadd s0, s0, s3
fadd s1, s1, s4
fadd s2, s2, s5
ret
Add4Vectors(vec3*, vec3*): // @Add4Vectors(vec3*, vec3*)
ldp q0, q1, [x0]
ldp q2, q3, [x1]
ldr q4, [x0, #32]
ldr q5, [x1, #32]
fadd v0.4s, v0.4s, v2.4s
fadd v1.4s, v1.4s, v3.4s
fadd v2.4s, v4.4s, v5.4s
stp q0, q1, [x0]
str q2, [x0, #32]
ret
Add16Vectors(vec3*, vec3*): // @Add16Vectors(vec3*, vec3*)
ldp q0, q1, [x0]
ldp q2, q3, [x1]
ldp q4, q5, [x0, #32]
ldp q6, q7, [x1, #32]
ldp q16, q17, [x0, #64]
ldp q18, q19, [x1, #64]
ldp q20, q21, [x0, #96]
ldp q22, q23, [x1, #96]
fadd v0.4s, v0.4s, v2.4s
fadd v1.4s, v1.4s, v3.4s
ldp q2, q3, [x0, #128]
fadd v4.4s, v4.4s, v6.4s
fadd v5.4s, v5.4s, v7.4s
ldp q6, q7, [x1, #128]
fadd v16.4s, v16.4s, v18.4s
fadd v17.4s, v17.4s, v19.4s
ldp q18, q19, [x0, #160]
fadd v20.4s, v20.4s, v22.4s
fadd v21.4s, v21.4s, v23.4s
ldp q22, q23, [x1, #160]
fadd v2.4s, v2.4s, v6.4s
fadd v3.4s, v3.4s, v7.4s
stp q0, q1, [x0]
fadd v6.4s, v18.4s, v22.4s
fadd v7.4s, v19.4s, v23.4s
stp q4, q5, [x0, #32]
stp q16, q17, [x0, #64]
stp q20, q21, [x0, #96]
stp q2, q3, [x0, #128]
stp q6, q7, [x0, #160]
ret
Add64Vectors(vec3*, vec3*): // @Add64Vectors(vec3*, vec3*)
stp x20, x19, [sp, #-32]! // 8-byte Folded Spill
stp x29, x30, [sp, #16] // 8-byte Folded Spill
add x29, sp, #16 // =16
mov x19, x1
mov x20, x0
bl Add16Vectors(vec3*, vec3*)
add x0, x20, #192 // =192
add x1, x19, #192 // =192
bl Add16Vectors(vec3*, vec3*)
add x0, x20, #384 // =384
add x1, x19, #384 // =384
bl Add16Vectors(vec3*, vec3*)
ldp x29, x30, [sp, #16] // 8-byte Folded Reload
add x0, x20, #576 // =576
add x1, x19, #576 // =576
ldp x20, x19, [sp], #32 // 8-byte Folded Reload
b Add16Vectors(vec3*, vec3*)
如果您问我,这很简洁,尤其是Add16Vectors例程,我认为我不会写 手工制作的更好的版本。
但是,仍然有一些东西用Add64Vectors例程困扰着我。 似乎不需要将寄存器保存在堆栈中。 要避免这种情况,编译器似乎一点也不复杂,因为有很多可用的寄存器。
这是我的写法: 我想念什么吗?
Add64Vectors(vec3*, vec3*):
mov x3, x1
mov x2, x0
bl Add16Vectors(vec3*, vec3*)
add x0, x2, #192
add x1, x3, #192
bl Add16Vectors(vec3*, vec3*)
add x0, x2, #384
add x1, x3, #384
bl Add16Vectors(vec3*, vec3*)
add x0, x2, #576
add x1, x3, #576
b Add16Vectors(vec3*, vec3*)
现在,我对此有点挑剔,但这是丑陋的部分。
我不想在这里发布太长的消息,因此请尝试在您这边查看结果。 (在https://godbolt.org上) 但是,我认为Clang产生的aarch64版本是唯一认为编译器做得很好的地方。
用于ARM64的GCC(8.2)在同一例程中使用的指令大约是用于ARM64的MSVC的两倍。
但这也许是因为目标不如x64主流吗? 不。
x64的结果令人震惊,即使Clang似乎也是赢家,我也不明白为什么x64版本是两倍长 作为aarch64版本...
我正在考虑对齐问题或缺少编译标志?