2013-01-13 88 views
1

我尝试学习大中央调度(GCD),并使用下面的代码进行测试:为什么GCD增加执行时间?

随着GCD:

#include <dispatch/dispatch.h> 
#include <vector> 
#include <cstdlib> 
#include <iostream> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    __block std::vector<int> a(N, 0); 
    dispatch_apply(N, 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
    ^(size_t i) 
    { 
     a[i] = i; 
#ifdef DEBUG   
     if (i % atoi(argv[2]) == 0) 
     std::cout << a[i] << std::endl; 
#endif 
    }); 
    return 0; 
} 

没有GCD:

#include <vector> 
#include <cstdlib> 
#include <iostream> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    std::vector<int> a(N, 0); 
    for (int i = 0; i < N; i++) 
    { 
     a[i] = i; 
#ifdef DEBUG 
     if (i % atoi(argv[2]) == 0) 
    std::cout << a[i] << std::endl; 
#endif 
    } 
return 0; 
} 

与GCD的测试结果:

$ time ./testgcd 100000000 10000000 
4.254 secs 

没有GCD的测试:

$ time ./nogcd 100000000 10000000 
1.462 secs 

我认为GCD应该减少执行时间,但结果却表明相反。 我不确定我是否滥用GCD。 OS环境是带有Xcode 4.5的Mac OS X 10.8。编译器是Clang ++ 3.1。 硬件是MacBook Pro与i5 CPU,它有两个核心。

为了比较,我使用的OpenMP(也使用在Xcode 4.5在相同的膝上型计算机随附GCC):

#include <vector> 
#include <cstdlib> 

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    std::vector <int> a(N, 0); 
    #pragma omp parallel for 
    for (int i = 0; i < N; i++) 
    a[i] = i; 
    return 0; 
} 

和W/W0(-fopenmp),我有两个可执行测试,

-fopenmp标志在编译:

$ time ./testopenmp 100000000 
1.280 secs 

没有-fopenmp标志在编译:

$ time ./testnoopenmp 100000000 
1.626 secs 

使用OpenMP,执行时间减少。

+0

想必您还没有为这些基准定义'#DEBUG'? –

+0

DEBUG块只是为了确保工作流程是正确的,我相信它与性能无关。 –

回答

7

GCD不一定要增加执行时间。之所以这样做是因为你做错了。重要的是,您要知道为什么您的应用程序首先是缓慢的。于是我就和多核分析器(Instruments.app)下运行你的代码,这里是它表明:

Multi-Core Profiling Screenshot

正如你所看到的,该图是多为黄色。黄色表示一个线程什么也不做,并等待某个任务执行。绿色表示它执行任务。换句话说,就是您编写代码的方式,应用程序花费了99%的时间来传递任务,而每个任务的执行几乎没有时间 - 开销太大。那么为什么会发生?

因为您已安排约100000000个任务运行。运行每个任务有一些开销,这远远大于将一个整数分配给一个数组。根据经验法则,如果任务的复杂度小于线程间通信的复杂度,则不会安排任务。

那么你如何解决这个问题?安排更少的任务,在每项任务中做更多。例如:

int main(int argc, char *argv[]) 
{ 
    const int N = atoi(argv[1]); 
    __block std::vector<int> a(N, 0); 
    dispatch_apply(4, 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
    ^(size_t iN) 
    { 
     size_t s = a.size()/4; 
     size_t i = (s*iN); 
     size_t n = i + s; 
     //printf("Iteration #%lu [%lu, %lu]\n", iN, i, n); 
     while (i < n) { 
      a[i] = i++; 
     } 
    }); 
    return 0; 
} 

现在,探查显示以下内容:

Not that bad

再次运行测试和GCD是有点快:

$ time ./test_nogcd 100000000 10000000 

real 0m0.516s 
user 0m0.378s 
sys 0m0.138s 
$ time ./test_gcd 100000000 10000000 

real 0m0.507s 
user 0m0.556s 
sys 0m0.138s 

或许运行更少的任务会更好吗?试试看。有了这样一个简单的工作流程,很可能使用单线程SIMD实现会更好。或者可能不是:)

请注意,在某些情况下您必须格外小心,例如,当总大小不能分成N等份等时,为简单起见,我省略了所有错误检查。

此外,当涉及到当今商品硬件上的并行任务时,还有很多细微之处。我建议你让自己熟悉MESI,虚假共享,内存障碍,CPU缓存,缓存遗漏算法等。并且记住 - 总是使用探查器!

希望它有帮助。祝你好运!

2

GCD不会神奇地减少总体执行时间,它的使用肯定是有代价的:思考,例如,关于事实之类的语句dispatch_apply_*,所有的幕后故事,现场管理,他们暗示,必须成本一些时间。 (现在,在我看来,2.5秒对于这样的管理来说太长了,但我现在无法评估你的结果的有效性)。 最终结果是GCD可能会提高您的性能,如果您正确使用它(在正确的场景中)并且您的硬件允许它。

可能的GCD的,导致你相信的特点是GCD以异步方式在一个单独的线程来执行任务的能力。 这,本身在这两种情况下,并不必然导致更短的总执行时间,但它可以帮助改善应用程序的响应,例如,不允许UI冻结。除此之外,如果CPU有更多的内核,或者你有一个多CPU系统,那么线程被安排在不同的核心/ CPU上,那么GCD可能会缩短总体执行时间,因为两个(实际上最多核心数量)不同的任务将并行执行。在这种情况下,两个任务的总持续时间将等于较长的任务持续时间(+管理成本)。

一旦澄清了这一点,进入更详细的关于你的榜样,你还可以看到以下内容:

  1. 要在同一辅助线程调度N个任务:这些任务将依次甚至在执行多核系统;

  2. 唯一的另一个线程正在运行,它并没有做任何冗长的事情,所以程序的总体持续时间由第1点任务的持续时间唯一确定;

  3. 最后,如果考虑到任务的性质,您会发现它只是一个执行N次的任务。现在,在GCD的情况下,为每个这样的任务排队一个任务,然后在辅助线程上执行它;在非GCD的情况下,你只是迭代一个for循环来执行N个赋值,这给你所有的最快时间。在前一种情况下,对于每项任务,您还要支付排队等待时间。

也许这并不是你可能要衡量GCD的利益,而这可能是一个很好的衡量GCD的在性能方面的成本的最显著的场景(它看起来像一个最差 - 我的情况)。

相关问题