6

所以我意识到这个问题听起来很愚蠢(是的,我使用的是双核),但是我尝试了两个不同的库(Grand Central Dispatch 和 OpenMP),并且在使用 clock() 来对代码进行计时时使用和不使用使它平行,速度是一样的。(为了记录,他们都使用自己的并行形式)。他们报告在不同的线程上运行,但也许他们在同一个核心上运行?有什么方法可以检查吗?(这两个库都是用于 C 的,我在较低层感到不舒服。)这非常奇怪。有任何想法吗?

4

6 回答 6

19

编辑:为响应 OP 评论添加了 Grand Central Dispatch 的详细信息。

虽然这里的其他答案通常很有用,但您问题的具体答案是您不应该clock()用来比较时间。clock()测量跨线程累加的 CPU 时间。当您在内核之间拆分作业时,它至少使用同样多的 CPU 时间(由于线程开销,通常会多一点)。在此页面上搜索clock() ,找到“如果进程是多线程的,则添加进程的所有单个线程消耗的cpu时间”。

只是作业是在线程之间拆分的,因此您必须等待的总时间更少。您应该使用挂钟时间(挂钟上的时间)。OpenMP 提供了一个例程omp_get_wtime()来执行此操作。以下面的例程为例:

#include <omp.h>
#include <time.h>
#include <math.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int i, nthreads;
    clock_t clock_timer;
    double wall_timer;
    for (nthreads = 1; nthreads <=8; nthreads++) {
        clock_timer = clock();
        wall_timer = omp_get_wtime();
        #pragma omp parallel for private(i) num_threads(nthreads)
        for (i = 0; i < 100000000; i++) cos(i);
        printf("%d threads: time on clock() = %.3f, on wall = %.3f\n", \
            nthreads, \
            (double) (clock() - clock_timer) / CLOCKS_PER_SEC, \
            omp_get_wtime() - wall_timer);
    }
}

结果是:

1 threads: time on clock() = 0.258, on wall = 0.258
2 threads: time on clock() = 0.256, on wall = 0.129
3 threads: time on clock() = 0.255, on wall = 0.086
4 threads: time on clock() = 0.257, on wall = 0.065
5 threads: time on clock() = 0.255, on wall = 0.051
6 threads: time on clock() = 0.257, on wall = 0.044
7 threads: time on clock() = 0.255, on wall = 0.037
8 threads: time on clock() = 0.256, on wall = 0.033

可以看到clock()时间变化不大。我得到 0.254 没有pragma,所以使用带有一个线程的 openMP 比完全不使用 openMP 慢一点,但是每个线程的墙时间都会减少。

例如,由于部分计算不是并行的(请参阅Amdahl's_law)或不同的线程争夺相同的内存,因此改进并不总是那么好。

编辑:对于 Grand Central Dispatch,GCD 参考说明 GCDgettimeofday用于挂墙时间。因此,我创建了一个新的 Cocoa 应用程序,并在其中applicationDidFinishLaunching输入:

struct timeval t1,t2;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int iterations = 1; iterations <= 8; iterations++) {
    int stride = 1e8/iterations;
    gettimeofday(&t1,0);
    dispatch_apply(iterations, queue, ^(size_t i) { 
        for (int j = 0; j < stride; j++) cos(j); 
    });
    gettimeofday(&t2,0);
    NSLog(@"%d iterations: on wall = %.3f\n",iterations, \
                t2.tv_sec+t2.tv_usec/1e6-(t1.tv_sec+t1.tv_usec/1e6));
}

我在控制台上得到以下结果:

2010-03-10 17:33:43.022 GCDClock[39741:a0f] 1 iterations: on wall = 0.254
2010-03-10 17:33:43.151 GCDClock[39741:a0f] 2 iterations: on wall = 0.127
2010-03-10 17:33:43.236 GCDClock[39741:a0f] 3 iterations: on wall = 0.085
2010-03-10 17:33:43.301 GCDClock[39741:a0f] 4 iterations: on wall = 0.064
2010-03-10 17:33:43.352 GCDClock[39741:a0f] 5 iterations: on wall = 0.051
2010-03-10 17:33:43.395 GCDClock[39741:a0f] 6 iterations: on wall = 0.043
2010-03-10 17:33:43.433 GCDClock[39741:a0f] 7 iterations: on wall = 0.038
2010-03-10 17:33:43.468 GCDClock[39741:a0f] 8 iterations: on wall = 0.034

这和我上面说的差不多。

这是一个非常人为的例子。事实上,您需要确保将优化保持在 -O0,否则编译器将意识到我们不保留任何计算并且根本不执行循环。此外,我cos在两个示例中采用的整数不同,但这不会对结果产生太大影响。请参阅STRIDE手册页上的dispatch_apply有关如何正确执行此操作以及为什么在这种情况下与此iterations大致可比的信息。num_threads

编辑:我注意到雅各布的回答包括

我在我的并行循环中使用 omp_get_thread_num() 函数来打印出它正在处理的核心......这样你就可以确定它在两个核心上运行。

这是不正确的(已通过编辑部分修复)。Usingomp_get_thread_num()确实是确保您的代码是多线程的好方法,但它不会显示“它正在处理哪个内核”,而只是显示哪个线程。例如,下面的代码:

#include <omp.h>
#include <stdio.h>

int main() {
    int i;
    #pragma omp parallel for private(i) num_threads(50)
    for (i = 0; i < 50; i++) printf("%d\n", omp_get_thread_num());
}

打印出它正在使用线程 0 到 49,但这并没有显示它正在处理哪个内核,因为我只有八个内核。通过查看活动监视器(OP 提到 GCD,所以必须在 Mac -go 上Window/CPU Usage),您可以看到内核之间的作业切换,因此 core != thread.

于 2010-03-09T01:44:02.267 回答
8

您的执行时间很可能不受您并行化的那些循环的限制。

我的建议是你分析你的代码,看看大部分时间是什么。大多数工程师会告诉你,在做任何剧烈的优化事情之前你应该这样做。

于 2010-03-08T23:05:06.620 回答
2

没有任何细节很难猜测。也许您的应用程序甚至不受 CPU 限制。您是否在代码运行时观察 CPU 负载?它是否在至少一个核心上达到 100%?

于 2010-03-08T23:06:21.953 回答
1

您的问题缺少一些非常重要的细节,例如您的应用程序的性质是什么,您要改进的部分是什么,分析结果(如果有的话)等等......

话虽如此,在进行性能改进工作时,您应该记住几个关键点:

  • 努力应始终集中在已通过 profiling证明是低效的代码区域
  • 并行化 CPU 绑定代码几乎永远不会提高性能(在单核机器上)。您将在不必要的上下文切换上浪费宝贵的时间而一无所获。通过这样做,您可以很容易地降低性能。
  • 即使您在多核机器上并行化 CPU 绑定代码,您也必须记住,您永远无法保证并行执行。

确保你没有违背这些观点,因为有根据的猜测(除非有任何额外的细节)会说这正是你正在做的事情。

于 2010-03-08T23:51:48.343 回答
0

如果您在循环内使用大量内存,则可能会阻止它更快。您也可以查看 pthread 库,以手动处理线程。

于 2010-03-08T23:08:37.427 回答
0

如果您不omp_get_thread_num()指定. 例如,num_threads

printf("Computing bla %d on core %d/%d ...\n",i+1,omp_get_thread_num()+1,omp_get_max_threads());

以上将适用于这个 pragma #pragma omp parallel for default(none) shared(a,b,c)

通过这种方式,您可以确保它在两个内核上运行,因为只会创建 2 个线程。

顺便说一句,编译时是否启用了 OpenMP?在 Visual Studio 中,您必须在Property Pages中启用它,C++ -> Language并设置OpenMP SupportYes

于 2010-03-08T23:37:09.267 回答