2016-12-01 62 views
1

我刚刚读了this intro与openMP并行处理。为什么不是多线程循环更快?

我尝试以下简单的代码

#include <iostream> 
#include <ctime> 
#include <vector> 

int main() 
{ 
     // Create an object just to allow the following loops to do something 
     std::vector<int> a; 
     a.reserve(2000); 

     // First single threaded loop 
     std::clock_t begin; 
     std::clock_t end; 
     begin = std::clock(); 
     double elapsed_secs; 

     for(int n=0; n<1000000000; ++n) 
     { 
       if (n%100000000 == 0) a.push_back(n); 
     } 

     end = std::clock(); 

     elapsed_secs = double(end - begin)/CLOCKS_PER_SEC; 
     std::cout << "Time for single thread loop: " << elapsed_secs << std::endl; 

     // Second multithreaded loop 
     begin = std::clock(); 
     #pragma omp parallel for 
     for(int n=0; n<1000000000; ++n) 
     { 
       if (n%100000000 == 0) a.push_back(n); 
     } 

     end = std::clock(); 
     elapsed_secs = double(end - begin)/CLOCKS_PER_SEC; 
     std::cout << "Time for multi thread loop: " << elapsed_secs << std::endl; 

     return 0; 
} 

已编译g++ -std=c++11 -o a a.cpp -fopenmp其输出

Time for single thread loop: 3.9438 
Time for multi thread loop: 3.94977 
  • 难道我误解如何并行在C++
  • 难道我误解如何编译?
  • 代码是否并行,但速度的提高并不明显,无论出于何种原因?

请注意,我的机器上有12个内核(并且没有大的进程正在运行)。

+0

它可能是一个优化问题? –

+0

你有一个依赖关系,因为'a'在每个循环处理之间是通用的。 – Jarod42

+3

什么是更快,翻转开关千次或有千人谁每次翻转同一开关一次?我很惊讶平行版本并不慢。猜测OMP做了理智的事情,并没有产生任何额外的线程。 –

回答

1

你不是测量实时,但CPU时间std::clock。建议使用std::chrono作为其他答案。

或者进行快速测试,而无需更改代码,试试这个在shell:

date; time ./a; date 

这是输出:

珏DIC 1二十三时12分57秒CET 2016

单线程循环时间:2.99741

多线程循环时间:4.55788

真正0m4.184s

用户0m7.556s

SYS 0m0.000s

珏DIC 1 23点13分01秒CET 2016

的时间不同,从你的输出。我的电脑实际时间大约是4s,而不是程序输出的7.5s。

,应该阅读的文档约std::clock(),具体有:

例如,如果CPU是由其他进程共享,性病::时钟时间 可以前进比挂钟慢。另一方面,如果当前的 进程是多线程的,并且多于一个的执行核心是 可用,则std :: clock时间可能比壁钟提前。

0

我认为你的第二个循环的原因比较慢,只是从线程所需的开销。你所做的工作非常少,而且往往会很快。 Push_back是不变的,函数只是使用一个指向容器末尾的指针来添加一个新的项目,然后它更新指向'结束'的指针。我认为如果你将更复杂的代码放入循环中,你会开始看到不同之处。我也注意到你永远不会清除循环之间的'a',所以你要向'a'中增加一百万个项目,这可能会导致第二个循环(即使它没有线程化)运行得更慢。

我不认为你误会了线程,就像你忽略了线程所需的开销(包括创建,inti,上下文swtiches,ext)。

这个问题似乎很常见,但同时每个人的答案可能会与很多不同的东西进入线程(https://unix.stackexchange.com/questions/80424/why-using-more-threads-makes-it-slower-than-using-less-threads)非常不同。在该链接中,答案更为广泛,指出线程速度非常依赖于系统性能,例如CPU资源,RAM资源和网络I/O资源。这个链接:Why is my multi-threading slower than my single threading?表明OP正在写入控制台,问题出在哪里(根据链接,控制台类处理线程同步,所以内置类中的代码是使单线程运行更快的原因)。

1

std::clock测量CPU时间,而不是挂墙时间(至少gcc的实现,尽管我相信MSVC实现测量挂壁时间)。这是从cppreference摘录:

返回从与程序执行相关的实现定义时代开始以来进程使用的近似处理器时间。将结果值转换为秒将其除以CLOCKS_PER_SEC

只有不同调用std :: clock返回的两个值之间的差别才有意义,因为std::clock时代的开始不必与程序的开始一致。根据操作系统给予程序的执行资源,时间可能比挂钟时间更快或更慢。例如,如果CPU被其他进程共享,则std::clock时间可能会比挂钟慢。另一方面,如果当前进程是多线程的并且有多个执行核心可用,则std::clock时间可能比挂钟提前得更快。

可以测量墙体的时间与std::chrono设施:

auto Begin = std::chrono::high_resolution_clock::now(); 
// ... 
auto End = std::chrono::high_resolution_clock::now(); 
std::cout << "Time for xxx: " << std::chrono::duration_cast<std::chrono::milliseconds>(End - Begin).count() << std::endl; 

,你会看到真正的加速。

作为一个方面说明,我会说你的测试不是线程安全的,因为push_back需要修改你的向量的位置end

0

还应该说,你在并行循环中做什么是非感性的,并且会导致运行时错误。有两个问题。

首先

#pragma omp parallel for 
for(int n=0; n<1000000000; ++n) 
{ if(n%100000000 == 0) <some code> } 

永远只能做任何事情10()次。编译器可以优化循环变量n路程,留给你的代码等同于

#pragma omp parallel for 
for(int n=0; n<10; ++n) 
{ <some code> } 

只受益于并行如果<some code>是非常苛刻的计算。所以,你其实没有测试任何东西

,以及更严重的是,

a.push_back(n); 

不是线程安全的。也就是说,你不能从不同的线程同步调用它(可能)。每次调用std::vector::push_back()都会改变向量的状态,即其内部数据,从而导致竞争状况。最后,我建议不要使用OpenMP与C++并行,因为它不支持/利用C++语言特性(比如模板),甚至没有为最近的C++标准进行标准化。而是使用类似于为C++设计的tbb之类的东西。