我创建了一个非常简单的测试程序,该程序可以计算某些标量c和矩阵A的c * A。您可以在此处在线运行它,也可以将以下代码粘贴到您喜欢的文本编辑器中:
#include <iostream>
#include <time.h>
#include <chrono>
#include <thread>
void fill_rand_matrix(double* mat, int n){
for (int i=0;i<n;i++){
mat[i]=static_cast <double> (rand()) / static_cast <double> (RAND_MAX)*20-10;
}
}
void test(size_t m, size_t n, double alpha, double* X) {
for (int j = 0; j < m; ++j) {
for (int i = 0; i < n; ++i) {
X[i+ j*n] *= alpha;
}
}
}
int main()
{
int m=10000;
int n=10000;
double res_scaling=0.5;
double* res=new double[m*n];
fill_rand_matrix(res,n*m);
auto begin1 = std::chrono::steady_clock::now();
std::thread t1(test,0.5*m,n,res_scaling,res);
std::thread t2(test,0.5*m,n,res_scaling,(double*)(res+(m/2)*n));
t1.join();
t2.join();
auto end1= std::chrono::steady_clock::now();
std::cout << "Time taken multithreaded = " << std::chrono::duration_cast<std::chrono::milliseconds>(end1 - begin1).count() << "[ms]" << std::endl;
auto begin2 = std::chrono::steady_clock::now();
test(m,n,res_scaling,res);
auto end2= std::chrono::steady_clock::now();
std::cout << "Time taken singlethreaded = " << std::chrono::duration_cast<std::chrono::milliseconds>(end2 - begin2).count() << "[ms]" << std::endl;
return 0;
}
当我多次运行此代码时,多线程版本仅比单线程变体快一点,甚至更慢。如果我添加两个以上的线程,甚至会发生这种情况。即使该问题几乎可以随内核数量完美地扩展,多线程似乎也没有任何好处。此外,我设置的矩阵大小越高,运行时间的波动就越剧烈,有时多达20倍。
你知道这里发生了什么吗?
我没有足够的知识来给出明确的答案,但是由于到目前为止还没有答案,我会尽力而为:
在大小为L的数组(其中L大于最大缓存的大小)上的多个顺序迭代将无法利用任何缓存来对数组进行新的缓存行访问(因为缓存使用了变体的LRU)。通过快速计算在大小为L的数组上快速迭代意味着访问(主)内存是瓶颈,并且将占用所有正在运行的进程/线程的共享内存总线。添加更多受主内存访问限制的线程只会导致它们之间的竞争。
(如果非常聪明,您的缓存可能会在将每个新数组数据传递到L2缓存之前就丢弃它们,这是因为您意识到在将其推出之前,您将无法使用它。这不会影响此过程,但会为其他人留下更多的缓存空间。)
对我来说, g++ -std=c++17 -O3 -pthread -S -fverbose-asm
...为汇编输出提供movups
与该行两次关联的指令:
X[i+ j*n] *= alpha; // line 17
movups
是x86并行化(SIMD)指令。这样的SIMD指令通常将4 double
s放入一个巨大的寄存器中,以非常快速地进行计算(总共约10个时钟周期,但如果我错了,请纠正我)。将此乘以4,即可在高速缓存行上完成工作:约40个周期。如果您不使用x86,则可能是使用具有类似并行化指令的CPU。
主内存访问速度很慢(从主内存中获取高速缓存行大约需要100-200个时钟周期[高速缓存行= 64字节的块〜= 16倍)]。
你的CPU将尝试预取数据,以帮助你,但是,因为你总是以更快的速度比数据总线可以提供请求从主内存中的数据,然后预取可能不仅有助于减少您的等待时间从说〜100至〜60,如果您幸运的话。无论哪种方式,等待主内存访问仍然是瓶颈。
请注意,这也可以从另一个方向进行,您可以使用更新的数组值填充缓存,但是在8MB左右之后,您将需要不断地将该数据发送回主内存。因此,实际上我们的上述估计是乐观的。
该test
函数有一个小错误:
j < m
并且i < n
是单/无符号比较。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句