c - 为什么我的计算机在使用并行代码时没有显示加速?

标签 c parallel-processing openmp performance grand-central-dispatch

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

最佳答案

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

虽然这里的其他答案通常很有用,但您问题的具体答案是您不应该使用 clock() 来比较时间。 clock() 测量跨线程累加的 CPU 时间。当您在内核之间拆分作业时,它至少会使用同样多的 CPU 时间(由于线程开销,通常会多一些)。在 this 上搜索 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()时间没有太大变化。我在没有 pragma 的情况下得到 0.254,因此在一个线程中使用 openMP 比根本不使用 openMP 要慢一点,但是每个线程的挂起时间都会减少。

改进不会总是这么好,例如,您的部分计算不是并行的(请参阅 Amdahl's_law)或不同的线程争用同一内存。

编辑:对于 Grand Central Dispatch,GCD reference指出,GCD 使用 gettimeofday 作为挂钟时间。因此,我创建了一个新的 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 的整数在两个示例中是不同的,但这不会对结果产生太大影响。请参阅 dispatch_apply 联机帮助页上的 STRIDE 以了解如何正确执行此操作以及为什么 iterationsnum_threads 具有广泛的可比性> 在这种情况下。

编辑:我注意到 Jacob 的回答包括

I use the omp_get_thread_num() function within my parallelized loop to print out which core it's working on... This way you can be sure that it's running on both cores.

这是不正确的(已通过编辑部分修复)。使用 omp_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 上 - 转到 Window/CPU Usage),您可以看到作业在核心之间切换,所以核心 != 线程。

关于c - 为什么我的计算机在使用并行代码时没有显示加速?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2405461/

相关文章:

gradle - 当并行,正常任务混合在一起时,并行任务执行会破坏顺序

c - 加载按分隔符分隔的字符串到数组

c++ - C 和 C++ 之间的联系有何不同?

c - 递增嵌套数组 - 带有日期信息

c++ - 在允许并行性的情况下用 STL 算法替换 for 循环

parallel-processing - 使用 openmp 优化 N-queen

c - 在声明符外声明参数

amazon-ec2 - Ray 未在 EC2 上启动 worker

c openmp - while 循环的并行化

c - 使用 OpenMP 并行化 C 代码后的性能损失