【问题标题】:Dual CPU memory allocation performance双CPU内存分配性能
【发布时间】:2016-10-10 10:11:03
【问题描述】:

当我在具有 2 个 CPU 的特定服务器上使用多线程代码时,我遇到了问题。该服务器在 Windows 7 x64 上运行,配备 Bi-Xeon E5-2697Wv2 12 Cores 2,7 GHz; 64 Gb RAM(8X8 Gb 1866 MHz);主板超微 X9DAI。我的可执行文件是使用 Visual Studio MSVC 2013 生成的,并使用 OpenMP 进行多线程处理。

现在的问题是我使用 1 线程而不是 24 线程的性能更好......这个问题仅在这台计算机上可见,当我连接分析器 (CodeXL) 时,我得到以下结果:

  • 1 个线程:~3% 的执行时间在 malloc/free(~3/~2) 内
  • 24 个线程:~64% 的执行时间在 malloc/free(~33%/~31%) 内

代码很复杂,我不能发布示例,但基本上它是一个蒙特卡罗代码,有少量动态分配(初始化阶段创建所有需要的数据),它仍然只是一个动态分配一个事件的开始,用于存储事件数据。代码不包含任何互斥体,每个线程除了在计算开始和结束时无需任何通信即可工作。

我在服务器和双 CPU 架构方面的知识非常有限,我想知道是否可以采取一些措施来避免这个问题(BIOS 选项?),我猜是有一个控制器可以选择哪个 CPU RAM用过,它这个操作让速度变慢了……

感谢您的阅读。

编辑: 我写了一个小基准来评估 malloc/free 的性能下降,这里是代码:

#include <omp.h>
#include <afx.h>
#include <vector>
#include <fstream>
#include <iostream>
#include <chrono>

// malloc allocation size tab
int allocSize[] =
{
    4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072
};

int main()
{
    // number max of thread
    int nbThreadMax = omp_get_max_threads();
    // malloc/free iteration per bench
    unsigned int nbIteration = 1000000;

    // Empty res tab
    std::vector<double> emptyRes(16, 0.);
    // Duration per thread
    std::vector<std::vector<double>> avgDuration(nbThreadMax, emptyRes);

    int nbThread = 1;
    unsigned int idxt = 0;
    while (nbThread <= nbThreadMax)
    {
        // Current bench result
        std::vector<std::vector<double>> threadResult(nbThread, emptyRes);

        std::cout << "Thread : " << nbThread << std::endl;

        // Create parrallel region
        #pragma omp parallel num_threads(nbThread)
        {
            int nt = omp_get_thread_num();

            for (unsigned int i = 0; i < 16; ++i)
            {
                int allocationSize = allocSize[i];

                std::chrono::time_point<std::chrono::system_clock> start, end;
                start = std::chrono::system_clock::now();
                for (unsigned int j = 0; j < nbIteration; ++j)
                {
                    void* pData = malloc(allocationSize);
                    free(pData);
                }
                end = std::chrono::system_clock::now();

                threadResult[nt][i] += std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 1000.;
            }
        }

        // Sum
        for (unsigned int i = 0; i < 16; ++i)
        {
            for (unsigned int j = 0; j <= idxt; ++j)
            {
                avgDuration[idxt][i] += threadResult[j][i];
            }
            // /!\  Normalize for one thread /!\
            avgDuration[idxt][i] /= nbThread;
        }

        ++idxt;
        // Increase thread number (X2)
        if (nbThread >= nbThreadMax)
            break;
        if (nbThread * 2 > nbThreadMax)
            nbThread = nbThreadMax;
        else
            nbThread = nbThread * 2;
    }

    // Write results
    {
        std::ofstream ofs("resultats.csv");
        ofs << "NbThread;";
        for (unsigned int i = 0; i < 16; ++i)
        {
            ofs << allocSize[i] << ";";
        }
        ofs << std::endl;

        int nbThread = 1;
        for (unsigned int n = 0; n < idxt; ++n)
        {
            ofs << nbThread << ";";
            for (unsigned int i = 0; i < 16; ++i)
            {
                ofs << avgDuration[n][i] << ";";
            }
            ofs << std::endl;
            nbThread = nbThread * 2;
        }

        ofs.close();
    }
}

这是在我的服务器上选择的结果: malloc/free duration /thread malloc/free performance factor /thread

这种结果是显示问题还是正常的性能下降?

【问题讨论】:

  • 2 个 CPU 上的 24 个线程?
  • 线程不是性能的圣杯。特别是在 NUMA 机器上
  • 是的,2 个 CPU 上有 24 个线程,我没有准确地说我有 8X8Gb 的 RAM。所以你的意思是我什么都做不了?
  • @πάνταῥεῖ 由于每个 CPU 有 12 个内核,因此非常合理
  • @user3513887 我忽略了每个都有 12 个核心。

标签: c++ multithreading performance memory-management cpu


【解决方案1】:

BIOS 选项太奇特了。最简单的解决方案是稍微偏离标准 C 方法并使用本机 Windows 方法。

第一个测试是将malloc/free 替换为HeapAlloc。这里的好处是HeapAlloc 可以支持多个堆,而HEAP_NO_SERIALIZE 每个堆都可以是单线程的。这确实意味着您必须在同一个线程上调用HeapFree。您可以在工作线程上调用HeapAlloc,将结果存储在分配的内存块中,与主线程连接(此处为内存屏障),然后在主线程上收集来自工作线程的所有数据并从主线程。由于工作线程不再存在,因此不存在序列化风险。

第二个改进(如有必要)是检查 NUMA 支持。最好将线程固定到 CPU 并从连接到该特定 CPU 的 4xGB 分配内存。但这要复杂得多。

【讨论】:

    【解决方案2】:

    标准 C11/C++11 中的一种方法是每个线程创建一个单项缓存。在mymalloc 中,检查单个缓存条目是否可以满足请求(不需要锁定)。如果没有,请按照常规malloc

    大部分情报都在myfree 中。如果已经有一个缓存条目,您需要决定做什么:保持最旧、保持最新、保持最小、保持最大,或者可能采用其他策略。 (如果您需要这里的大小,mymalloc 必须超额分配 sizeof(size_t) 并在请求的大小前面加上前缀)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-05
      • 1970-01-01
      • 2016-06-22
      • 1970-01-01
      • 2020-04-22
      • 2010-11-23
      • 1970-01-01
      • 2012-02-09
      相关资源
      最近更新 更多