使用2个线程的代码比使用1的代码运行慢6倍

use*_*501 10 c++ optimization performance multithreading

原始问题:

所以我编写了一些代码来试验线程并进行一些测试.

代码应创建一些数字,然后找到这些数字的平均值.

我想现在向你展示我所拥有的东西会更容易.我期待两个线程的代码运行速度大约是其两倍.用秒表测量它我觉得它运行速度慢了6倍!编辑:现在使用计算机和clock()函数来告诉时间.

void findmean(std::vector<double>*, std::size_t, std::size_t, double*);


int main(int argn, char** argv)
{

    // Program entry point
    std::cout << "Generating data..." << std::endl;

    // Create a vector containing many variables
    std::vector<double> data;
    for(uint32_t i = 1; i <= 1024 * 1024 * 128; i ++) data.push_back(i);

    // Calculate mean using 1 core
    double mean = 0;
    std::cout << "Calculating mean, 1 Thread..." << std::endl;
    findmean(&data, 0, data.size(), &mean);
    mean /= (double)data.size();

    // Print result
    std::cout << "  Mean=" << mean << std::endl;

    // Repeat, using two threads
    std::vector<std::thread> thread;
    std::vector<double> result;
    result.push_back(0.0);
    result.push_back(0.0);
    std::cout << "Calculating mean, 2 Threads..." << std::endl;

    // Run threads
    uint32_t halfsize = data.size() / 2;
    uint32_t A = 0;
    uint32_t B, C, D;
    // Split the data into two blocks
    if(data.size() % 2 == 0)
    {
        B = C = D = halfsize;
    }
    else if(data.size() % 2 == 1)
    {
        B = C = halfsize;
        D = hsz + 1;
    }

    // Run with two threads
    thread.push_back(std::thread(findmean, &data, A, B, &(result[0])));
    thread.push_back(std::thread(findmean, &data, C, D , &(result[1])));

    // Join threads
    thread[0].join();
    thread[1].join();

    // Calculate result
    mean = result[0] + result[1];
    mean /= (double)data.size();

    // Print result
    std::cout << "  Mean=" << mean << std::endl;

    // Return
    return EXIT_SUCCESS;
}


void findmean(std::vector<double>* datavec, std::size_t start, std::size_t length, double* result)
{
    for(uint32_t i = 0; i < length; i ++) {
        *result += (*datavec).at(start + i);
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这段代码并不完美,如果你能提出改进方法,那么我也会感激不尽.

注册变量:

有几个人建议为函数'findmean'创建一个局部变量.这就是我所做的:

void findmean(std::vector<double>* datavec, std::size_t start, std::size_t length, double* result)
{
register double holding = *result;
for(uint32_t i = 0; i < length; i ++) {
    holding += (*datavec).at(start + i);
}
*result = holding;
}
Run Code Online (Sandbox Code Playgroud)

我现在可以报告:代码运行的执行时间几乎与单个线程相同.这是6倍的大改进,但肯定必须有一种方法使它几乎快两倍?

注册变量和O2优化:

我已将优化设置为'O2' - 我将创建一个包含结果的表.

结果到目前为止:

原始代码没有优化或寄存器变量:1个线程:4.98秒,2个线程:29.59秒

添加了寄存器变量的代码:1线程:4.76秒,2个线程:4.76秒

使用reg变量和-O2优化:1线程:0.43秒,2线程:0.6秒2线程现在更慢?

有了Dameon的建议,即在两个结果变量之间放置一大块内存:1线程:0.42秒,2个线程:0.64秒

TAS建议使用迭代器来访问向量的内容:1线程:0.38秒,2线程:0.56秒

与Core i7 920(单通道内存4GB)相同:1线程:0.31秒,2线程:0.56秒

与Core i7 920(双通道内存2x2GB)相同:1线程:0.31秒,2线程:0.35秒

Mys*_*ial 19

为什么2个线程比1个线程慢6倍?

你被错误的虚假分享案件所打击.

在摆脱了错误共享后,为什么2个线程不比1个线程快?

你的内存带宽会让你陷入困境.


虚假分享:

这里的问题是每个线程正在访问result相邻存储器位置的变量.它们很可能落在同一个高速缓存行上,因此每次线程访问它时,它都会在核心之间反弹高速缓存行.

每个线程都在运行此循环:

for(uint32_t i = 0; i < length; i ++) {
    *result += (*datavec).at(start + i);
}
Run Code Online (Sandbox Code Playgroud)

您可以看到result变量经常被访问(每次迭代).因此,每次迭代时,线程都在为同一个高速缓存行而奋斗,这个高速缓存行保持两个值result.

通常,编译器应放入*result寄存器,从而删除对该内存位置的常量访问.但是,由于您从未开启过优化,因此编译器很可能仍在访问内存位置,从而在循环的每次迭代中都会产生错误共享惩罚.

内存带宽:

一旦你消除了错误的共享并摆脱了6倍的减速,你没有得到改进的原因是因为你已经超出你的内存带宽.

确定您的处理器可能是4个内核,但它们都共享相同的内存带宽.总结一个数组的特定任务对每次内存访问都做了很少的(计算)工作.单个线程已足以最大化您的内存带宽.因此,转向更多线程不太可能让您获得更多改进.

简而言之,通过向其投入更多线程,您将无法以更快的速度对数组求和.