You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

查表法正弦估算函数使用float类型时性能反劣于double类型的原因排查求助

查表法正弦估算函数使用float类型时性能反劣于double类型的原因排查求助

我最近写了一个用查表法实现的简易正弦估算函数,出于好奇同时测试了float和double两种版本——本来以为float版本因为数据更紧凑、缓存命中率更高会跑得更快,但实际结果完全相反:float版耗时7.715秒,double版只需要6.411秒,这个差异很明显而且每次测试都稳定重现。

先贴出我实现的double版本查表函数:

double fastSinD(double x) {
    int sinIndex = x * (0.5f * SINE_TABLE_SIZE / M_PI);
    double delta = x - sinIndex * (2.f * M_PI / SINE_TABLE_SIZE);
    int cosIndex = sinIndex + SINE_TABLE_SIZE/4;
    sinIndex &= SINE_TABLE_SIZE-1;
    cosIndex &= SINE_TABLE_SIZE-1;
    double sinVal = sineTable_4q[sinIndex];
    double cosVal = sineTable_4q[cosIndex];
    return sinVal + (cosVal - 0.5f*sinVal*delta) * delta;
}

float版本的实现完全一致,只是把所有double类型换成float,查表用float数组,PI也用float版本。

我一开始怀疑是不是代码里有隐式的float转double转换拖慢了速度,但看生成的汇编代码发现并没有这种情况。下面是double版本的汇编输出:

fastSinD:
  .seh_endprologue
  vmulsd .LC4(%rip), %xmm0, %xmm1
  leaq sineTable_4q(%rip), %rcx
  vcvttsd2sil %xmm1, %eax
  vxorps %xmm1, %xmm1, %xmm1
  leal 128(%rax), %edx
  vcvtsi2sdl %eax, %xmm1, %xmm1
  andl $511, %eax
  vfnmadd132sd .LC5(%rip), %xmm0, %xmm1
  vmovsd (%rcx,%rax,8), %xmm2
  movl %edx, %eax
  vmulsd .LC6(%rip), %xmm2, %xmm0
  andl $511, %eax
  vfnmadd213sd (%rcx,%rax,8), %xmm1, %xmm0
  vfmadd132sd %xmm1, %xmm2, %xmm0
  ret

float版本的汇编几乎完全一样,只是把sd指令换成ss,对应的表地址也换成float版的:

fastSinF:
  .seh_endprologue
  vmulss .LC7(%rip), %xmm0, %xmm1
  leaq f_sineTable_4q(%rip), %rcx
  vcvttss2sil %xmm1, %eax
  vxorps %xmm1, %xmm1, %xmm1
  leal 128(%rax), %edx
  vcvtsi2ssl %eax, %xmm1, %xmm1
  andl $511, %eax
  vfnmadd132ss .LC8(%rip), %xmm0, %xmm1
  vmovss (%rcx,%rax,4), %xmm2
  movl %edx, %eax
  vmulss .LC9(%rip), %xmm2, %xmm0
  andl $511, %eax
  vfnmadd213ss (%rcx,%rax,4), %xmm1, %xmm0
  vfmadd132ss %xmm1, %xmm2, %xmm0
  ret

这两段汇编结构完全一致,说明函数本身没有问题,那为什么float版会更慢呢?

后来我按评论区的建议,把调用函数也加入了基准测试,终于发现了问题所在。先贴出我扩展后的基准测试代码:

void benchmarkCallingFunc() {
    struct timespec start, finish;
    int data[10000];
    float samples[10000];
    for (int i = 0; i < 10000; i++) {
        samples[i] = ((i*91) % 100000) / 1000.f;
    }

    clock_gettime(CLOCK_REALTIME, &start);
    double result = 0;
    for (int n = 0; n < 10; n++) {
        for (int k = 0; k < 10000; k++) {
            double sum = 0;
            for (int i = 0; i < 4095; i++) {
                double dt = samples[(k+n)%10000]*10 - i;
                float sinc = dt == 0 ? 1 : fastSinD(M_PI * dt) / (M_PI * dt);
                sum += (double)data[(i*k+n) % 10000] * sinc;
            }
            result += sum;
        }
    }
    printf("%f\n", result);
    clock_gettime(CLOCK_REALTIME, &finish);
    printf("Double fast sin took %d milliseconds.\n", (finish.tv_sec - start.tv_sec)*1000 + (finish.tv_nsec - start.tv_nsec) / 1000000L);

    clock_gettime(CLOCK_REALTIME, &start);
    result = 0;
    for (int n = 0; n < 10; n++) {
        for (int k = 0; k < 10000; k++) {
            double sum = 0;
            for (int i = 0; i < 4095; i++) {
                float dt = samples[(k+n)%10000]*10 - i;
                float sinc = dt == 0 ? 1 : fastSinF(M_PI * dt) / (M_PI * dt);
                sum += (float)data[(i*k+n) % 10000] * sinc;
            }
            result += sum;
        }
    }
    printf("%f\n", result);
    clock_gettime(CLOCK_REALTIME, &finish);
    printf("Float fast sin took %d milliseconds.\n", (finish.tv_sec - start.tv_sec)*1000 + (finish.tv_nsec - start.tv_nsec) / 1000000L);
}

用这段代码测试时,double版耗时约1450ms,float版则需要1870ms;但我把float版本调用代码里的M_PI换成float字面量3.14159265f之后,float版的耗时直接降到了1242ms,性能提升了接近50%!

原来问题出在math.h里定义的M_PI是double类型的字面量,在float版本的调用逻辑中,用M_PI和float类型的dt相乘时,会触发大量隐式的float转double转换,这些转换的开销累积起来就导致了性能的大幅下降——我之前只检查了查表函数本身的汇编,完全没注意到调用侧的这个细节。

最后补充一下我的编译命令:

gcc -fdiagnostics-color=always -O3 -march=native benchmark.c -o benchmark.exe

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:43:13