【问题标题】:Odd discrepancy in CPU vs Wall time measurements with malloc使用 malloc 进行 CPU 与 Wall time 测量的奇怪差异
【发布时间】:2021-05-19 15:11:42
【问题描述】:

答案:用户和内核cpu时间是分开测量的,小心!

编辑:pTimes 现在在每个基准测试之间重置为零,但结果变得更奇怪了!

以摆弄我自己的自定义内存管理方案为最终目标,我为 Visual Community 2019、Windows 10 中现有的 malloc() 做了一个简单的基准测试。出于兴趣,我对 CPU 时间和挂墙时间进行了基准测试,我通过在许多块中分配一大块内存来测试 malloc,然后单独释放每个块而不使用它们。见这里:

void malloc_distr256(int nMemsize) {
    long long pFreeList[256];

    for (int i = 0; i < 256; ++i) pFreeList[i] = malloc(nMemsize >> 8);
    for (int i = 0; i < 256; ++i) free((void*)pFreeList[i]);
}

void malloc_distr64(int nMemsize) {
    long long pFreeList[64];

    for (int i = 0; i < 64; ++i) pFreeList[i] = malloc(nMemsize >> 6);
    for (int i = 0; i < 64; ++i) free((void*)pFreeList[i]);
}

void malloc_distr0(int nMemsize) {
    void* pMem = malloc(nMemsize);

    free(pMem);
}

我使用以下代码对这些函数进行了基准测试 - “BenchTimes”只是一个拥有双倍 CPU/wall 时间的结构:

inline double cputime() {
    FILETIME lpCreationTime;
    FILETIME lpExitTime;
    FILETIME lpKernelTime;
    FILETIME lpUserTime;

    if (GetProcessTimes(GetCurrentProcess(), &lpCreationTime, &lpExitTime, &lpKernelTime, &lpUserTime)) {
        double dUnits = (double)(lpUserTime.dwLowDateTime | (long long)lpUserTime.dwHighDateTime << 32);
        return dUnits * 0.1;
    }
    else return 0xFFF0000000000000;
}

inline double walltime() {
    LARGE_INTEGER lnFreq, lnTime;

    if (QueryPerformanceFrequency(&lnFreq)) if (QueryPerformanceCounter(&lnTime))
        return 1000000.0 * (double)lnTime.QuadPart / (double)lnFreq.QuadPart;
//multiply by 1,000,000 to convert seconds to microseconds
//because the cpu time measurer I had in microseconds as well
    return 0.0;
}

void bench(void (pfnFunc)(int), int nMemsize, int nIters, int nReps, BenchTimes* pTimes) {
    pTimes->dCpuTime = 0.0;
    pTimes->dWallTime = 0.0;

    for (volatile int r = 0; r < nReps; ++r) {
        double dCpuStart = cputime();
        double dWallStart = walltime();

        for (volatile int i = 0; i < nIters; ++i) pfnFunc(nMemsize);

        double dCpuEnd = cputime();
        double dWallEnd = walltime();

        double dCpuDiff = dCpuEnd - dCpuStart;
        double dWallDiff = dWallEnd - dWallStart;

        pTimes->dCpuTime += dCpuDiff;
        pTimes->dWallTime += dWallDiff;
    }
}

这些是在我的电脑 (i5-9400f) 上测量的时间,以秒为单位。

我对性能和 Wall time 与 CPU 时间比较的巨大差异感到非常好奇!

运行它的代码在这里:

    BenchTimes sTimes;

    bench(malloc_distr256, 1 << 20, 100, 1000, &sTimes);

    fprintf(stdout, "Malloc alloc/free bench allocated %lf megabytes, distributed over 256 chunks\n", (double)(1 << 20) / 1000000);
    fprintf(stdout, "Malloc alloc/free bench returned:\nWalltime - total: %lf\nCPU Time - total: %lf\n", sTimes.dWallTime / 1000000, sTimes.dCpuTime / 1000000);
    
    bench(malloc_distr64, 1 << 20, 100, 1000, &sTimes);

    fprintf(stdout, "\nMalloc alloc/free bench allocated %lf megabytes, distributed over 64 chunks\n", (double)(1 << 20) / 1000000);
    fprintf(stdout, "\nMalloc alloc/free bench returned:\nWalltime - total: %lf\nCPU Time - total: %lf\n", sTimes.dWallTime / 1000000, sTimes.dCpuTime / 1000000);

    bench(malloc_distr0, 1 << 20, 100, 1000, &sTimes);

    fprintf(stdout, "\nMalloc alloc/free bench allocated %lf megabytes, distributed over no chunks\n", (double)(1 << 20) / 1000000);
    fprintf(stdout, "\nMalloc alloc/free bench returned:\nWalltime - total: %lf\nCPU Time - total: %lf\n", sTimes.dWallTime / 1000000, sTimes.dCpuTime / 1000000);

    system("pause");

【问题讨论】:

  • 为什么是long long pFreeList[256];?为什么不void *pFreeList[256];
  • @Someprogrammerdude 点了-但这不是问题的重点
  • 既然你没有显示minimal reproducible example(你如何、何时以及在哪里打电话给bench?)我们所能做的就是猜测,但我的猜测是你没有重置pTimes 的成员在通话之间。
  • @Someprogrammerdude 你的猜测是正确的,我是个傻瓜——谢谢
  • @Someprogrammerdude 我进行了必要的更改,但结果仍然不一致!我即将发布最小的可重现示例

标签: c windows memory malloc cpu-time


【解决方案1】:

malloc 是通过HeapAlloc 实现的,它在名为RtlAllocateHeap 的系统函数中实现。

函数管理堆。它通过VirtualAlloc[Ex] 或其等价物分配系统内存页面,并在这些页面内提供更小的分配。

对于较大的分配,每次分配都会调用 VirtualAlloc[Ex] 等效项,对于较小的分配,它会偶尔调用。

VirtualAlloc[Ex] 是使用NtAllocateVirtualMemory 实现的,这是内核调用。大部分时间都没有计入lpUserTime

另一方面,QueryPerformanceCounter 是一个诚实的总时间。

【讨论】:

  • 那么当执行一个没有分块的 1MB malloc 时,该进程将在内核中花费多达两秒钟?
  • 但是对于 256 和 64 分配的工作台,它只在内核中花费 0.2 秒?我不明白差异
  • @IdioticShrike,实际上你可以自己检查。 GetProcessTimes 也返回内核时间。添加另一个函数,或者让你的 cputime() 返回两个值
  • 这使得墙时间和 cpu 时间每次都在 0.1 秒之内 - 感谢您指出这一点
  • 更大的分配(显着大于系统内存页面,即 65536 字节)可能确实比小的占用更多的内核时间,因为系统必须在进程的虚拟地址空间中找到连续的部分
猜你喜欢
  • 2021-05-26
  • 2011-11-12
  • 1970-01-01
  • 2018-04-10
  • 2015-06-05
  • 2011-01-21
  • 1970-01-01
相关资源
最近更新 更多