13

原始问题:

所以我写了一些代码来试验线程并做一些测试。

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

我认为向您展示我到目前为止所拥有的东西更容易。我期望使用两个线程,代码的运行速度大约是原来的 2 倍。用秒表测量它,我认为它的运行速度慢了大约 6 倍!编辑:现在使用计算机和时钟()函数来告诉时间。

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);
    }
}

我不认为这段代码非常棒,如果你能提出改进它的方法,那么我也将不胜感激。

注册变量:

有几个人建议为函数“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;
}

我现在可以报告:代码运行的执行时间几乎与单线程相同。这是 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 秒

4

4 回答 4

19

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

您受到虚假分享的严重打击。

摆脱虚假共享后,为什么 2 线程不比 1 线程快?

您受到内存带宽的限制。


虚假分享:

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

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

for(uint32_t i = 0; i < length; i ++) {
    *result += (*datavec).at(start + i);
}

您可以看到该result变量被非常频繁地访问(每次迭代)。因此,每次迭代,线程都在为保存result.

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

内存带宽:

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

当然,您的处理器可能是 4 个内核,但它们都共享相同的内存带宽。您对数组求和的特定任务对每次内存访问几乎没有(计算)工作。单个线程已经足以最大化您的内存带宽。因此,去更多的线程不太可能让你有很大的进步。

简而言之,不,您将无法通过向其抛出更多线程来显着加快对数组求和的速度。

于 2013-06-27T16:21:50.287 回答
2

如其他答案所述,您在结果变量上看到错误共享,但还有另一个位置发生这种情况。std::vector<T>::at()函数(以及std::vector<T>::operator[]())在每个元素访问时访问向量的长度。为避免这种情况,您应该切换到使用迭代器。此外, usingstd::accumulate()将允许您利用您正在使用的标准库实现中的优化。

以下是代码的相关部分:

thread.push_back(std::thread(findmean, std::begin(data)+A, std::begin(data)+B, &(result[0])));
thread.push_back(std::thread(findmean, std::begin(data)+B, std::end(data), &(result[1])));

void findmean(std::vector<double>::const_iterator start, std::vector<double>::const_iterator end, double* result)
{
    *result = std::accumulate(start, end, 0.0);
}

这始终为我的 32 位上网本上的两个线程提供了更好的性能。

于 2013-06-27T19:07:59.990 回答
1

更多线程并不意味着更快!创建和上下文切换线程存在开销,即使运行此代码的硬件也会影响结果。对于像这样微不足道的工作,最好是单线程。

于 2013-06-27T16:22:22.687 回答
0

这可能是因为启动和等待两个线程的成本比在单个循环中计算结果要多得多。您的数据大小为 128MB,这对于现代处理器在单个循环中处理来说并不多。

于 2013-06-27T16:21:31.393 回答