双CPU内存分配性能

时间:2016-10-10 10:11:03

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

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

现在问题是我使用1个线程而不是24个线程有更好的性能......这个问题只在这台计算机上可见,当我附加一个探查器(CodeXL)时,我得到以下结果:

  • 1个帖子:〜3%的执行时间在malloc / free(~3 / ~2)内
  • 24个主题:~64%的执行时间在malloc / free内(~33%/ ~31%)

代码很复杂,我不能发布一个例子,但基本上它是一个蒙特卡罗代码,有一些动态分配(初始化阶段创建所有需要的数据),它仍然只是一个动态在一个事件开始时分配以存储事件数据。代码不包含任何互斥锁,除了在计算的开始和结束之外,每个线程都没有任何通信。

我对服务器和双CPU架构的了解非常有限,我想知道是否有什么办法可以避免这个问题(BIOS选项?),我猜是有一个控制器可以选择哪个CPU内存使用它,这个操作慢下来......

感谢您的阅读。

编辑: 我写了一个小基准来评估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 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:0)

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

大部分情报都在myfree。如果已经有一个缓存条目,您需要决定做什么:保持最旧,保持最新,保持最小,保持最大,或可能是其他策略。 (如果您需要此处的大小,mymalloc必须按sizeof(size_t)过度分配并为请求的大小添加前缀。