请考虑以下代码。它运行nThreads
个线程将浮点数从data1
复制到data2
。它似乎没有加速nThreads
增加,甚至工作得更慢。我认为它可能与线程创建开销有关,因此将数组的大小增加到疯狂的值,但它仍然没有加速。然后我读到了false sharing,但似乎只有当错误的共享数据彼此足够接近以适应高速缓存行时才会出现这种情况,绝对不会超过数百兆字节。
#include <iostream>
#include <thread>
#include <cstring>
#include <sys/time.h>
static inline long double currentTime()
{
timespec ts;
clock_gettime(CLOCK_MONOTONIC,&ts);
return ts.tv_sec+(long double)(ts.tv_nsec)*1e-9;
}
void mythread(float* timePrev, float* timeNext, int kMin, int kMax)
{
for(int q=0;q<16;++q) // take more time
for(int k=kMin;k<kMax;++k)
timeNext[k]=timePrev[k];
}
static inline void runParallelJob(float* timePrev, float* timeNext, int W, int H, int nThreads)
{
std::thread* threads[nThreads];
int total=W*H;
int perThread=total/nThreads;
for(int t=0;t<nThreads;++t)
{
int k0=t*perThread;
int k1=(t+1)*perThread;
threads[t]=new std::thread(mythread,timePrev,timeNext,k0,k1);
}
for(int t=0;t<nThreads;++t)
{
threads[t]->join();
delete threads[t];
}
}
int main()
{
size_t W=20000,H=10000;
float* data1=new float[W*H];
float* data2=new float[W*H];
memset(data1,0,W*H*sizeof(float));
memset(data2,0,W*H*sizeof(float));
for(float nThreads=1;nThreads<=8;++nThreads)
{
long double time1=currentTime();
runParallelJob(data1, data2, W, H, nThreads);
long double time2=currentTime();
std::cerr << nThreads << " threads: " << (time2-time1)*1e+3 << " ms\n";
}
}
我用g ++ 4.5.1编译这个程序,命令为g++ main.cpp -o threads -std=c++0x -O3 -lrt && ./threads
。我的Core i7 930(带有超线程的四核)上的这个程序的输出如下:
1个主题:5426.82 ms
2个主题:5298.8 ms
3个主题:5865.99 ms
4个主题:5845.62 ms
5个主题:5827.3 ms
6个主题:5843.36 ms
7个主题:5919.97 ms
8个主题:5862.17 ms
最初,该程序被缩减到这个测试用例,在线程循环中进行了一些乘法,除法和加法,而不是普通复制,具有相同的性能。
有趣的是,如果我从编译器命令行中省略-O3
,则1个线程似乎执行11303 ms而2个线程执行6398 ms(~2x加速),但更多线程仍执行约5700 ms(否)更多加速)。
所以,我的问题是:我错过了什么?为什么性能在我的情况下不随线程数而变化?
答案 0 :(得分:4)
我认为限制复制速度的因素是内存带宽。因此,让多个内核复制数据没有任何区别,因为所有线程都必须共享相同的内存带宽。
答案 1 :(得分:0)
通常,在给定任务中抛出更多线程并不一定会使其更快。管理线程和上下文切换的开销很昂贵,因此需要有一个特定的理由来实现此路由。等待一些I / O(数据库调用,服务调用,磁盘访问)是使用其他线程的一个常见原因,高CPU任务可以从多核机器上的线程中受益,并确保用户通过以下方式保持对应用程序的控制专用UI线程是另一种情况。