用原子非常慢的C++ 11 STD线程 我想学习使用VC++ 11的STD::用VS2012的线程,我用一个简单的增加计数器的两个线程编写了一个非常简单的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; }

用原子非常慢的C++ 11 STD线程 我想学习使用VC++ 11的STD::用VS2012的线程,我用一个简单的增加计数器的两个线程编写了一个非常简单的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; },c++,c++11,visual-studio-2012,atomic,stdthread,C++,C++11,Visual Studio 2012,Atomic,Stdthread,现在,当我对计数器使用注释的long变量时,我得到的值与正确的-100000000不同,而不是20000000。我不知道为什么会这样,我想这两个线程正在同时改变计数器,但我不确定它到底是如何发生的,因为++只是一条非常简单的指令。线程似乎在开始时缓存sum变量。两个线程的性能为110毫秒,而一个线程的性能为200毫秒 因此,根据文档,正确的方法是使用std::atomic。但是,现在这两种情况下的性能都要差得多,大约3300毫秒没有线程,15820毫秒有线程。在这种情况下,使用std::atom

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

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

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

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

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

您正在为std::atomic提供的同步支付费用。它不会像使用不同步的整数那样快,不过通过优化add的内存顺序,可以稍微提高性能:

sum.fetch_add(1, std::memory_order_relaxed);
在本例中,您正在为x86编译并在64位整数上操作。这意味着编译器必须生成代码来更新两个32位操作中的值;如果将目标平台更改为x64,编译器将生成代码以在单个64位操作中执行增量操作

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

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

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

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

您正在为std::atomic提供的同步支付费用。它不会像使用不同步的整数那样快,不过通过优化add的内存顺序,可以稍微提高性能:

sum.fetch_add(1, std::memory_order_relaxed);
在本例中,您正在为x86编译并在64位整数上操作。这意味着编译器必须生成代码来更新两个32位操作中的值;如果将目标平台更改为x64,编译器将生成代码以在单个64位操作中执行增量操作


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

您的代码有几个问题。首先,所有涉及的输入都是编译时常量,因此一个好的编译器可以预先计算单线程代码的值,因此不管您为显示为在0毫秒内运行的范围提供了多少值

其次,您在所有线程之间共享一个变量和,强制在该点同步所有线程的访问。如果没有同步,则会产生未定义的行为。正如您已经发现的那样,同步对该变量的访问是非常昂贵的,因此如果合理的话,您通常希望避免它

一种方法是为每个线程使用单独的小计,这样所有线程都可以并行地进行相加,而无需同步,最后将各个结果相加

另一点是防止虚假共享。当两个或多个线程正在写入实际上是分离的但已分配在同一缓存线中的数据时,会出现错误共享。在这种情况下,对内存的访问可以序列化,即使如前所述,线程之间没有任何实际共享的数据

基于这些因素,我稍微重写了您的代码,为每个线程创建了一个单独的sum变量。这些变量属于类类型,可以相当直接地访问数据,但确实阻止了优化器看到它可以在编译时完成整个计算,因此我们最终比较了 ng一个线程到4个提醒我:我确实将线程数从2个增加到了4个,因为我使用的是四核机器。不过,我将该数字移到了一个常量变量中,因此使用不同数量的线程进行测试应该很容易

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

const int num_threads = 4;

struct val {
    long long sum;
    int pad[2];

    val &operator=(long long i) { sum = i; return *this; }
    operator long long &() { return sum; }
    operator long long() const { return sum; }
};

val sum[num_threads];

using namespace std;

const int RANGE = 100000000;

void test_without_threds()
{
    sum[0] = 0LL;
    for(unsigned int j = 0; j < num_threads; j++)
    for(unsigned int k = 0; k < RANGE; k++)
        sum[0] ++ ;
}

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

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

    //Join the threads with the main thread
    for (int i = 0; i < num_threads; ++i) {
        t[i].join();
    }
    long long total = std::accumulate(std::begin(sum), std::end(sum), 0LL);
}

int main()
{
    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 threads\n";

    start = chrono::system_clock::now();
    test_with_threads();
    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;
}

。。。总和是相同的,但是N个线程将速度提高了大约N倍,直到可用的内核数。

您的代码有几个问题。首先,所有涉及的输入都是编译时常量,因此一个好的编译器可以预先计算单线程代码的值,因此不管您为显示为在0毫秒内运行的范围提供了多少值

其次,您在所有线程之间共享一个变量和,强制在该点同步所有线程的访问。如果没有同步,则会产生未定义的行为。正如您已经发现的那样,同步对该变量的访问是非常昂贵的,因此如果合理的话,您通常希望避免它

一种方法是为每个线程使用单独的小计,这样所有线程都可以并行地进行相加,而无需同步,最后将各个结果相加

另一点是防止虚假共享。当两个或多个线程正在写入实际上是分离的但已分配在同一缓存线中的数据时,会出现错误共享。在这种情况下,对内存的访问可以序列化,即使如前所述,线程之间没有任何实际共享的数据

基于这些因素,我稍微重写了您的代码,为每个线程创建了一个单独的sum变量。这些变量属于类类型,可以相当直接地访问数据,但确实阻止了优化器看到它可以在编译时完成整个计算,因此我们最终将一个线程与4个线程进行比较,这提醒了我:我确实将线程数从2个增加到了4个,因为我使用的是四核机器。不过,我将该数字移到了一个常量变量中,因此使用不同数量的线程进行测试应该很容易

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

const int num_threads = 4;

struct val {
    long long sum;
    int pad[2];

    val &operator=(long long i) { sum = i; return *this; }
    operator long long &() { return sum; }
    operator long long() const { return sum; }
};

val sum[num_threads];

using namespace std;

const int RANGE = 100000000;

void test_without_threds()
{
    sum[0] = 0LL;
    for(unsigned int j = 0; j < num_threads; j++)
    for(unsigned int k = 0; k < RANGE; k++)
        sum[0] ++ ;
}

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

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

    //Join the threads with the main thread
    for (int i = 0; i < num_threads; ++i) {
        t[i].join();
    }
    long long total = std::accumulate(std::begin(sum), std::end(sum), 0LL);
}

int main()
{
    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 threads\n";

    start = chrono::system_clock::now();
    test_with_threads();
    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;
}

。。。总和是相同的,但N个线程会将速度提高大约N倍,直到可用的内核数。

尝试使用前缀增量,这将提高性能。
在我的机器上测试,std::memory\u order\u released没有任何优势。

尝试使用前缀increment,这将提高性能。
在我的机器上测试,std::memory\u order\u released没有任何优势。

您是在发布模式下编译的吗?如果没有原子,您的程序将具有未定义的行为。使用原子时,速度会慢一些,因为现在有两个线程一直在争夺同一个变量。多线程是非常昂贵的,而且只在某些特殊情况下才值得。即使没有线程,它也要慢得多。还有什么其他选择可以让它正确快速地工作呢?VS2012还有一个效率相当低的std::atomic实现。VS2013要好得多。将循环替换为sum+=范围;原子性能几乎没有那么重要。并发的一般规则是最小化争用,而不是毫无意义地最大化争用。您是在发布模式下编译的吗?如果没有原子,您的程序具有未定义的行为。使用原子时,速度会慢一些,因为现在有两个线程一直在争夺同一个变量。多线程是非常昂贵的,而且只在某些特殊情况下才值得。即使没有线程,它也要慢得多。还有什么其他选择可以让它正确快速地工作呢?VS2012还有一个效率相当低的std::atomic实现。VS2013要好得多。将循环替换为sum+=范围;原子性能几乎没有那么重要。并发的一般规则是最小化争用,而不是毫无意义地最大化争用。如何从两个32位原子操作中生成64位原子操作?fetch_add对我来说没有更快的效果,同样,原子计算也同样慢,即使没有线程,我想两个处理器都使用自己的寄存器?它们在开始时和一段时间后将值复制回共享内存。@Yakk:它涉及使用lock cmpxchg8b指令进行旋转,该指令对8字节内存进行原子比较和交换。加法是通过add和adc add with CARGER指令在两个32位寄存器上执行的。@BajMile:使用std::atomic可确保线程在每次操作后必须将其结果提交回内存。在这种情况下,两条线相互踩在对方的脚趾上。他们都在争夺谁来更新同一块内存。@collin这是一个64位原子操作,用于更新值。我猜有两个32位机器代码操作来生成要更新的值。原子运算是不可分解的,这是我的观点。如何从两个32位的原子运算中生成64位的原子运算?fetch_add对我来说没有更快的效果,同样,原子运算也同样慢,即使没有线程,我想两个处理器都是usi
他们自己登记吗?它们在开始时和一段时间后将值复制回共享内存。@Yakk:它涉及使用lock cmpxchg8b指令进行旋转,该指令对8字节内存进行原子比较和交换。加法是通过add和adc add with CARGER指令在两个32位寄存器上执行的。@BajMile:使用std::atomic可确保线程在每次操作后必须将其结果提交回内存。在这种情况下,两条线相互踩在对方的脚趾上。他们都在争夺谁来更新同一块内存。@collin这是一个64位原子操作,用于更新值。我猜有两个32位机器代码操作来生成要更新的值。原子操作是不可分解的,这是我的观点。是的,如果线程使用自己的计数器,它就会工作。没有异步工作的方法,也无法正确快速地工作。谢谢。是的,如果线程使用它自己的计数器,它就会工作。没有异步工作的方法,也无法正确快速地工作。谢谢