C++ 11 std 线程求和与原子非常慢

Baj*_*ile 1 c++ atomic c++11 stdthread visual-studio-2012

我想学习在 VS2012 中使用 C++ 11 std::threads,我写了一个非常简单的 C++ 控制台程序,它有两个线程,它只是增加一个计数器。我还想测试使用两个线程时的性能差异。测试程序如下:

#include <iostream>
#include <thread>
#include <conio.h>
#include <atomic>

std::atomic<long long> sum(0);
//long long sum;

using namespace std;

const int RANGE = 100000000;

void test_without_threds()
{
    sum = 0;
    for(unsigned int j = 0; j < 2; j++)
    for(unsigned int k = 0; k < RANGE; k++)
        sum ++ ;
}

void call_from_thread(int tid) 
{
    for(unsigned int k = 0; k < RANGE; k++)
        sum ++ ;
}

void test_with_2_threds()
{
    std::thread t[2];
    sum = 0;
    //Launch a group of threads
    for (int i = 0; i < 2; ++i) {
        t[i] = std::thread(call_from_thread, i);
    }

    //Join the threads with the main thread
    for (int i = 0; i < 2; ++i) {
        t[i].join();
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    chrono::time_point<chrono::system_clock> start, end;

    cout << "-----------------------------------------\n";
    cout << "test without threds()\n";

    start = chrono::system_clock::now();
    test_without_threds();
    end = chrono::system_clock::now();

    chrono::duration<double> elapsed_seconds = end-start;

    cout << "finished calculation for "
              << chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
              << "ms.\n";

    cout << "sum:\t" << sum << "\n";\

    cout << "-----------------------------------------\n";
    cout << "test with 2_threds\n";

    start = chrono::system_clock::now();
    test_with_2_threds();
    end = chrono::system_clock::now();

    cout << "finished calculation for "
              << chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
              << "ms.\n";

    cout << "sum:\t" << sum << "\n";\

    _getch();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在,当我仅使用 long long 变量(已注释)作为计数器时,我得到的值与正确的不同 - 100000000 而不是 200000000。我不知道为什么会这样,我想这两个线程正在改变计数器,但我不确定它是如何发生的,因为 ++ 只是一个非常简单的指令。似乎线程在开始时缓存 sum 变量。两个线程的性能为 110 毫秒,而一个线程的性能为 200 毫秒。

因此,根据文档,正确的方法是使用 std::atomic。然而,现在这两种情况的性能都要差得多,没有线程大约 3300 毫秒,有线程大约 15820 毫秒。在这种情况下使用 std::atomic 的正确方法是什么?

Col*_*nee 5

我不确定为什么会这样,我想两个线程同时更改计数器,但我不确定它是如何发生的,因为 ++ 只是一个非常简单的指令。

每个线程都将 的值拉sum入寄存器,递增寄存器,最后在循环结束时将其写回内存。

因此,根据文档,正确的方法是使用 std::atomic。然而,现在这两种情况的性能都要差得多,没有线程大约 3300 毫秒,有线程大约 15820 毫秒。在这种情况下使用 std::atomic 的正确方法是什么?

您正在为同步std::atomic提供的服务付费。它不会像使用非同步整数那么快,尽管您可以通过改进添加的内存顺序来获得性能的小幅改进:

sum.fetch_add(1, std::memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,您正在为 x86 进行编译并在 64 位整数上进行操作。这意味着编译器必须生成代码来更新两个 32 位操作中的值;如果将目标平台更改为 x64,编译器将生成代码以在单个 64 位操作中执行增量。

一般来说,解决此类问题的方法是减少共享数据的写入次数。