SIMD 性能看起来不太对劲

问题描述 投票:0回答:1

我一直在尝试改进本地计算机上基本循环的性能。总而言之,我有 2 个大的 float32 切片,并且希望使用任何可能的方法将它们相乘以获得最佳改进。作为参考,我有一个 3.7Ghz AMD 12 核心,运行频率约为 4.1Ghz

首先,for 循环内单个 mul 的基本实现产生:4.2B 操作/秒

基本循环展开产生了相同的结果(go 编译器标准优化看起来对我来说是展开的):

for i := 0; i < len(a); i += 4 {
    s0 := a[i] * b[i]
    s1 := a[i+1] * b[i+1]
    s2 := a[i+2] * b[i+2]
    s3 := a[i+3] * b[i+3]
    sum += s0 + s1 + s2 + s3
}

如果我在编译器中禁用越界检查,我会看到一个很大的改进:8.2B ops/秒问题是这是编译器默认的安全措施,所以我需要一种方法让编译器知道它不需要执行越界检查,这可以通过循环内的切片容量检查来完成,并提供7.6B操作/秒

的性能
for i := 0; i < len(a) && i < len(b); i += 4 {
    aTmp := a[i : i+4 : i+4]
    bTmp := b[i : i+4 : i+4]
    s0 := aTmp[0] * bTmp[0]
    s1 := aTmp[1] * bTmp[1]
    s2 := aTmp[2] * bTmp[2]
    s3 := aTmp[3] * bTmp[3]
    sum += s0 + s1 + s2 + s3

接下来我想走SIMD路线,并首先通过“github.com/bjwbell/gensimd/simd”库实现它:

for i := 0; i < len(a); i += 4 {
    a := simd.MulF32x4(simd.F32x4{a[i], a[i+1], a[i+2], a[i+3]}, simd.F32x4{b[i], b[i+1], b[i+2], b[i+3]})
    sum += a[0] + a[1] + a[2] + a[3]
}

理论上这应该是在 256 个宽寄存器上对每条指令执行 4 个乘法。结果显示只有 1.1b 次操作/秒,所以显然出了问题

我也使用 cgo 和 assembly 做了同样的事情:

转到文件

C.add_arrays((*C.float)(unsafe.Pointer(&a[0])), (*C.float)(unsafe.Pointer(&b[0])), C.int(len(a)))

c 文件:

void add_arrays(float* a, float* b, int len) {
__m256 va, vb, vsum;
for (int i = 0; i < len; i += 8) {
    va = _mm256_load_ps(a + i);
    vb = _mm256_load_ps(b + i);
    vsum = _mm256_add_ps(va, vb);
    _mm256_store_ps(a + i, vsum);
}
}

产生2.9B 操作/秒

我希望 SIMD 比展开版本快几倍,我对 go 实现的编码是否错误或遗漏了什么?我对此比较陌生,所以任何建议都会很好。

go simd cgo
1个回答
0
投票

看起来编译器正在分解乘法,因为结果和没有保存/使用。修复所有四个测试以存储和使用总和后,我现在看到使用内在函数从 SIMD 获得更多内联结果。即使使用工具/生成/构建,Gensimd lib 似乎也无法工作。

展开 - Bil 操作/秒:2.840884878822056

展开无边界检查 - Bil 操作/秒:2.963691811615894

SIMD gensimd - Bil 操作/秒:0.27859654205971995

SIMD 内在函数 - Bil 操作/秒:4.534921160395626

© www.soinside.com 2019 - 2024. All rights reserved.