C 用AVX内部函数重写math.h函数的性能改进

C 用AVX内部函数重写math.h函数的性能改进,c,math,gcc,compiler-optimization,avx,C,Math,Gcc,Compiler Optimization,Avx,我有一个简单的数学库,它链接到一个在模拟器硬件(32位RTOS)上运行的项目中,编译器工具链基于GCC 5.5的一个变体。主要项目代码在Matlab中,但核心数学运算(数组数据上的cmath函数)用C重新编写,以提高性能。查看编译器资源管理器,优化后的代码的质量对于(参考:)来说似乎不是很好。据我所知,Clang在优化循环方面做得更好。一个示例代码段: ... void cfunctionsLog10(unsigned int n, const double* x, double* y) {

我有一个简单的数学库,它链接到一个在模拟器硬件(32位RTOS)上运行的项目中,编译器工具链基于GCC 5.5的一个变体。主要项目代码在Matlab中,但核心数学运算(数组数据上的cmath函数)用C重新编写,以提高性能。查看编译器资源管理器,优化后的代码的质量对于(参考:)来说似乎不是很好。据我所知,Clang在优化循环方面做得更好。一个示例代码段:

...

void cfunctionsLog10(unsigned int n, const double* x, double* y) {
    int i;
    for (i = 0; i < n; i++) {
        y[i] = log10(x[i]);
    }
}
在Clang产生的地方:

cfunctionsLog10(unsigned int, double const*, double*):              # @cfunctionsLog10(unsigned int, double const*, double*)
        push    ebp
        push    ebx
        push    edi
        push    esi
        sub     esp, 76
        mov     esi, dword ptr [esp + 96]
        test    esi, esi
        je      .LBB2_8
        mov     edi, dword ptr [esp + 104]
        mov     ebx, dword ptr [esp + 100]
        xor     ebp, ebp
        cmp     esi, 4
        jb      .LBB2_7
        lea     eax, [ebx + 8*esi]
        cmp     eax, edi
        jbe     .LBB2_4
        lea     eax, [edi + 8*esi]
        cmp     eax, ebx
        ja      .LBB2_7
.LBB2_4:
        mov     ebp, esi
        xor     esi, esi
        and     ebp, -4
.LBB2_5:                                # =>This Inner Loop Header: Depth=1
        vmovsd  xmm0, qword ptr [ebx + 8*esi + 16] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp], xmm0
        vmovsd  xmm0, qword ptr [ebx + 8*esi] # xmm0 = mem[0],zero
        vmovsd  xmm1, qword ptr [ebx + 8*esi + 8] # xmm1 = mem[0],zero
        vmovsd  qword ptr [esp + 8], xmm0 # 8-byte Spill
        vmovsd  qword ptr [esp + 16], xmm1 # 8-byte Spill
        call    log10
        fstp    tbyte ptr [esp + 64]    # 10-byte Folded Spill
        vmovsd  xmm0, qword ptr [esp + 16] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        call    log10
        fstp    tbyte ptr [esp + 16]    # 10-byte Folded Spill
        vmovsd  xmm0, qword ptr [esp + 8] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        vmovsd  xmm0, qword ptr [ebx + 8*esi + 24] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp + 8], xmm0 # 8-byte Spill
        call    log10
        vmovsd  xmm0, qword ptr [esp + 8] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        fstp    qword ptr [esp + 56]
        fld     tbyte ptr [esp + 16]    # 10-byte Folded Reload
        fstp    qword ptr [esp + 48]
        fld     tbyte ptr [esp + 64]    # 10-byte Folded Reload
        fstp    qword ptr [esp + 40]
        call    log10
        fstp    qword ptr [esp + 32]
        vmovsd  xmm0, qword ptr [esp + 56] # xmm0 = mem[0],zero
        vmovsd  xmm1, qword ptr [esp + 40] # xmm1 = mem[0],zero
        vmovhps xmm0, xmm0, qword ptr [esp + 48] # xmm0 = xmm0[0,1],mem[0,1]
        vmovhps xmm1, xmm1, qword ptr [esp + 32] # xmm1 = xmm1[0,1],mem[0,1]
        vmovups xmmword ptr [edi + 8*esi + 16], xmm1
        vmovups xmmword ptr [edi + 8*esi], xmm0
        add     esi, 4
        cmp     ebp, esi
        jne     .LBB2_5
        mov     esi, dword ptr [esp + 96]
        cmp     ebp, esi
        je      .LBB2_8
.LBB2_7:                                # =>This Inner Loop Header: Depth=1
        vmovsd  xmm0, qword ptr [ebx + 8*ebp] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp], xmm0
        call    log10
        fstp    qword ptr [edi + 8*ebp]
        inc     ebp
        cmp     esi, ebp
        jne     .LBB2_7
.LBB2_8:
        add     esp, 76
        pop     esi
        pop     edi
        pop     ebx
        pop     ebp
        ret

由于我无法直接使用Clang,使用AVX intrinsics重新编写C源代码是否有价值。我认为大部分性能成本来自cmath函数调用,其中大多数没有内在实现


编辑: 使用以下方法重新实现:

void vclfunctionsTanh(无符号整数n,常数double*x,double*y)
{
常数int N=N;
const int VectorSize=4;
常数int FirstPass=N&(-VectorSize);
int i=0;
对于(;i
向量类库具有常见数学函数的内联向量版本,包括log10


首先,在你的问题中包括一些实际的代码+asm,而不仅仅是通过Godbolt短链接。(如果您没有包括所有内容,请使用Godbolt的“完整”链接来防止bitrot)。此外,使用更新的GCC可更有效地自动矢量化sqrt和/或
-mno-avx256-split-unaligned-load-mno-avx256-split-unaligned-store
(请参阅)。也可以尝试
-mfpmath=sse
?调用约定仍然在堆栈上传递,并以x87返回,除非重新编译libm。如果您只需要其中一些函数的快速近似值,您可以手动将它们矢量化,并使用内部函数内联;这可能会大大加快速度。尽管AVX和x87混合使用进行存储/重新加载的效率很低,但除了sqrt之外,大部分成本都在数学库函数中。所以,是的,并行进行4次双打至少可以快4倍,如果你以精度换取速度,速度甚至更高。(和/或NaN检查;如果您知道您的输入是有限的,这会有很大帮助,特别是对于不能对每个元素进行分支的SIMD)是的,根据您编写它们的方式,它们当然可以。log和exp相对容易矢量化,尾数的多项式近似值收敛很快。e、 g./和相关问答。IDK关于cos。对于
float
,您可以简单地测试每一个可能的32位浮点位模式,以确定最小/最大错误。此外,还有一些SIMD数学库和现有的实现,如//您的C
cfunctionsTanh
是一个不同于您显示的asm的函数,调用
cfunctionsLog10
。此外,clang的asm实际上非常糟糕,将返回值溢出/重新加载到堆栈中,而不是直接转换到目标。10字节x87存储/重新加载比双精度存储/重新加载慢得多,而且完全没有必要。文档建议不要使用
-ffast math
编译,在我们知道我们只处理有限数和不会溢出的运算的情况下,这是真的吗?-ffast math使得在大多数情况下无法检测NAN结果。如果您无论如何都不想检查NAN,那么可以使用-ffast math。
cfunctionsLog10(unsigned int, double const*, double*):              # @cfunctionsLog10(unsigned int, double const*, double*)
        push    ebp
        push    ebx
        push    edi
        push    esi
        sub     esp, 76
        mov     esi, dword ptr [esp + 96]
        test    esi, esi
        je      .LBB2_8
        mov     edi, dword ptr [esp + 104]
        mov     ebx, dword ptr [esp + 100]
        xor     ebp, ebp
        cmp     esi, 4
        jb      .LBB2_7
        lea     eax, [ebx + 8*esi]
        cmp     eax, edi
        jbe     .LBB2_4
        lea     eax, [edi + 8*esi]
        cmp     eax, ebx
        ja      .LBB2_7
.LBB2_4:
        mov     ebp, esi
        xor     esi, esi
        and     ebp, -4
.LBB2_5:                                # =>This Inner Loop Header: Depth=1
        vmovsd  xmm0, qword ptr [ebx + 8*esi + 16] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp], xmm0
        vmovsd  xmm0, qword ptr [ebx + 8*esi] # xmm0 = mem[0],zero
        vmovsd  xmm1, qword ptr [ebx + 8*esi + 8] # xmm1 = mem[0],zero
        vmovsd  qword ptr [esp + 8], xmm0 # 8-byte Spill
        vmovsd  qword ptr [esp + 16], xmm1 # 8-byte Spill
        call    log10
        fstp    tbyte ptr [esp + 64]    # 10-byte Folded Spill
        vmovsd  xmm0, qword ptr [esp + 16] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        call    log10
        fstp    tbyte ptr [esp + 16]    # 10-byte Folded Spill
        vmovsd  xmm0, qword ptr [esp + 8] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        vmovsd  xmm0, qword ptr [ebx + 8*esi + 24] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp + 8], xmm0 # 8-byte Spill
        call    log10
        vmovsd  xmm0, qword ptr [esp + 8] # 8-byte Reload
        vmovsd  qword ptr [esp], xmm0
        fstp    qword ptr [esp + 56]
        fld     tbyte ptr [esp + 16]    # 10-byte Folded Reload
        fstp    qword ptr [esp + 48]
        fld     tbyte ptr [esp + 64]    # 10-byte Folded Reload
        fstp    qword ptr [esp + 40]
        call    log10
        fstp    qword ptr [esp + 32]
        vmovsd  xmm0, qword ptr [esp + 56] # xmm0 = mem[0],zero
        vmovsd  xmm1, qword ptr [esp + 40] # xmm1 = mem[0],zero
        vmovhps xmm0, xmm0, qword ptr [esp + 48] # xmm0 = xmm0[0,1],mem[0,1]
        vmovhps xmm1, xmm1, qword ptr [esp + 32] # xmm1 = xmm1[0,1],mem[0,1]
        vmovups xmmword ptr [edi + 8*esi + 16], xmm1
        vmovups xmmword ptr [edi + 8*esi], xmm0
        add     esi, 4
        cmp     ebp, esi
        jne     .LBB2_5
        mov     esi, dword ptr [esp + 96]
        cmp     ebp, esi
        je      .LBB2_8
.LBB2_7:                                # =>This Inner Loop Header: Depth=1
        vmovsd  xmm0, qword ptr [ebx + 8*ebp] # xmm0 = mem[0],zero
        vmovsd  qword ptr [esp], xmm0
        call    log10
        fstp    qword ptr [edi + 8*ebp]
        inc     ebp
        cmp     esi, ebp
        jne     .LBB2_7
.LBB2_8:
        add     esp, 76
        pop     esi
        pop     edi
        pop     ebx
        pop     ebp
        ret

void vclfunctionsTanh(unsigned int n, const double* x, double* y) 
{
    const int N = n;
    const int VectorSize = 4;
    const int FirstPass = N & (-VectorSize);

    int i = 0;    
    for (; i < FirstPass; i+= 4)
    {
        Vec4d data = Vec4d.load(x[i]);
        Vec4d ans = tanh(data);
        ans.store(y+i);
    }

    
    for (;i < N; ++i)
        y[i]=std::tanh(x[i]);
}