You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Windows平台下C语言高精度代码耗时对比及低重复次数精度优化咨询

Windows平台下C语言高精度代码耗时对比及低重复次数精度优化咨询

嘿,老兄,完全懂你这种纠结——想用计时工具测代码速度,结果单次跑的波动大到离谱,甚至差10倍,要是遇到单次跑就要好几秒的代码,跑十万次简直要等到天荒地老!我给你几个简单易实现的法子,既能提升精度,又能大幅减少重复次数:


1. 拉大单次测量的时间窗口,降低相对误差

你现在每次只测N=10000次rand循环,耗时可能只有微秒级,而gettimeofday本身的精度(加上系统调度的噪声)就占了很大比例,波动自然大。咱们可以把内部循环重复多轮,打包成一次测量,再除以轮数得到单次的时间。比如把内部循环跑K=100次,这样单次测量的时间就从微秒级升到毫秒级,噪声的影响就被稀释了,重复次数M就能从10万直接降到100左右,结果还更稳定。

2. 跳过第一次运行的「热身」开销

程序第一次执行目标代码时,会有内存分配、缓存加载、动态链接库初始化这些额外开销,这部分时间根本不是你要测的代码耗时。把第一次的测量结果直接扔掉,只统计后面的正式测试数据,能去掉一大波异常值。

3. 别只看平均值,用中位数/百分位数更靠谱

平均值很容易被极端值(比如系统突然跳出来个后台任务抢资源,导致某次耗时暴增)带偏,中位数能反映最典型的性能表现,95%/99%百分位数还能帮你看到最坏情况的波动。这几个统计量一起看,比单独的平均值靠谱多了。

4. 给测试「降噪」:控制运行环境

  • 测试时关掉所有后台无关程序(浏览器、聊天软件、杀毒软件自动扫描啥的),避免系统资源被抢占;
  • 如果是Linux环境(你用了gettimeofday,Windows下建议换QueryPerformanceCounter,后面说),用taskset -c 0 ./你的程序把进程绑到单个CPU核心,避免线程切换带来的缓存失效和调度波动;
  • 尽量在系统 idle 的时候测试,别在编译大项目或者下载东西的时候跑。

针对你示例代码的修改版本

我把上面的思路整合到你的代码里了,你可以直接跑试试:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <sys/time.h>
#include <time.h>

// 用于qsort的比较函数,计算中位数用
int compare_doubles(const void *a, const void *b) {
    const double *da = (const double *)a;
    const double *db = (const double *)b;
    return (*da > *db) - (*da < *db);
}

double *Vec(int n)
{
    double *p = (double*)malloc(n * sizeof(double));
    assert(p != NULL);
    return p;
}

int main()
{
    int N = 10000;
    int K = 100; // 内部循环重复K次,拉大单次测量窗口
    int M = 100; // 重复次数从10万降到100,节省时间
    double* v = Vec(N);
    double* t = Vec(M);
    struct timeval begin, end;

    srand(time(0));

    // 热身:第一次运行,扔掉结果
    gettimeofday(&begin, 0);
    for (int k=0; k<K; k++) {
        for (int i=0; i<N; i++) {
            v[i] = rand() % 100;
        }
    }
    gettimeofday(&end, 0);

    // 正式测量
    for (int j=0; j<M; j++) 
    {
        gettimeofday(&begin, 0);
        for (int k=0; k<K; k++) {
            for (int i=0; i<N; i++)
            {
                v[i] = rand() % 100;
                //v[i] = rand()*100/(RAND_MAX +1.0);
            }
        }
        gettimeofday(&end, 0);
        long seconds = end.tv_sec - begin.tv_sec;
        long microseconds = end.tv_usec - begin.tv_usec;
        t[j] = (seconds + microseconds*1e-6) / K; // 除以K得到单次N循环的时间
    }

    // 计算平均值和中位数
    double tmean = 0;
    for (int j=0; j<M; j++)
    {
        tmean += t[j]/M;
    }
    qsort(t, M, sizeof(double), compare_doubles);
    double tmedian = t[M/2];

    printf("Mean time: %.6f ms.\n", tmean*1000);
    printf("Median time: %.6f ms.\n", tmedian*1000);

    free(v);
    free(t);

    return 0;
}

补充:Windows平台的高精度计时替换

你标题提到Windows平台,但gettimeofday不是Windows标准API,Windows下建议用QueryPerformanceCounter,精度比gettimeofday更高,替换起来也简单:

// 替换原来的gettimeofday测量部分
#include <windows.h>

LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq); // 获取计数器频率
QueryPerformanceCounter(&start);

// 你的目标代码块
for (int k=0; k<K; k++) {
    for (int i=0; i<N; i++) {
        v[i] = rand() % 100;
    }
}

QueryPerformanceCounter(&end);
double elapsed = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart;
t[j] = elapsed / K;

总结一下:核心就是让单次测量的时间足够长,把噪声的影响降到最低,再配合鲁棒的统计指标和干净的测试环境,不需要成千上万次重复就能得到可靠的性能对比结果。

备注:内容来源于stack exchange,提问作者D. Alfano

火山引擎 最新活动