【发布时间】:2013-09-06 16:08:43
【问题描述】:
考虑这个递归多线程程序:
#include <iostream>
#include <thread>
#define NUMTHREADS 4
using namespace std;
int g[NUMTHREADS];
thread t[NUMTHREADS];
void task1(int x)
{
if(x+1<NUMTHREADS)
t[x] = thread(task1, x+1);
for(int i=0;i<100000000;i++)
g[x]++;
if(x+1<NUMTHREADS)
t[x].join();
}
int main()
{
task1(0);
for(int i=0;i<NUMTHREADS;i++)
cout<<g[i]<<" ";
}
我预计线程开销是微不足道的,但实际上程序的运行时间随着线程数线性增加。
这是我的 6 核 cpu 的一些计时:
NUMTHREADS = 1:
$ time ./a
100000000
real 0m0.330s
user 0m0.312s
sys 0m0.015s
NUMTHREADS = 2:
$ time ./a
100000000 100000000
real 0m0.742s
user 0m1.404s
sys 0m0.015s
NUMTHREADS = 3:
$ time ./a
100000000 100000000 100000000
real 0m1.038s
user 0m2.792s
sys 0m0.000s
NUMTHREADS = 4:
$ time ./a
100000000 100000000 100000000 100000000
real 0m1.511s
user 0m5.616s
sys 0m0.015s
知道为什么会这样吗?
【问题讨论】:
-
您的假设是错误的,开销很大。在没有优化的情况下比较性能几乎没有意义,几乎任何基本的优化器都会用一条指令替换那个微不足道的循环。
-
为什么在没有优化的情况下比较性能毫无意义?我没有使用 -O2 并且时间反映循环仍在执行中。使用 -O2 执行 4 个线程大约需要 0.023 秒。因此,创建和加入 4 个线程不会产生 1.5 秒的开销。
-
-O2改变了很多东西,而不仅仅是循环。您还可能遇到false sharing 问题,每个 CPU 都在更新不同的位置,这些位置都落入同一缓存行;所以你投入工作的核心越多,它就越慢。尝试分散g的元素,使它们至少相隔64 个字节,看看是否有任何变化。即使您处理可能的缓存问题,您仍然会受到 RAM 带宽的限制(尤其是您的代码可能会受到内存 I/O 限制)。 -
你是对的,这是虚假分享。除了用虚拟变量手动填充代码之外,有没有办法避免这种情况?
-
太好了,我改变了循环,所以它可以与堆栈上的变量一起使用并且它是固定的。
标签: c++ concurrency