使用多线程时,速度低于预期

时间:2016-09-16 03:43:44

标签: c multithreading performance pthreads openmp

备注:我对此感到有些愚蠢,但这可能有助于某人

所以,我试图通过使用并行性来提高程序的性能。但是,我遇到了测量加速的问题。我有4个CPU:

~% lscpu
...
CPU(s):                4
...

然而,加速比低四倍。这是一个最小的工作示例,包含顺序版本,使用OpenMP的版本和使用POSIX线程的版本(确保它不是由于任何实现)。

纯粹顺序(add_seq.c):

#include <stddef.h>

int main() {
    for (size_t i = 0; i < (1ull<<36); i += 1) {
        __asm__("add $0x42, %%eax" : : : "eax");
    }
    return 0;
}

OpenMP(add_omp.c):

#include <stddef.h>

int main() {
    #pragma omp parallel for schedule(static)
    for (size_t i = 0; i < (1ull<<36); i += 1) {
        __asm__("add $0x42, %%eax" : : : "eax");
    }
    return 0;
}

POSIX线程(add_pthread.c):

#include <pthread.h>
#include <stddef.h>

void* f(void* x) {
    (void) x;
    const size_t count = (1ull<<36) / 4;
    for (size_t i = 0; i < count; i += 1) {
        __asm__("add $0x42, %%eax" : : : "eax");
    }
    return NULL;
}
int main() {
    pthread_t t[4];
    for (size_t i = 0; i < 4; i += 1) {
        pthread_create(&t[i], NULL, f, NULL);
    }
    for (size_t i = 0; i < 4; i += 1) {
        pthread_join(t[i], NULL);
    }
    return 0;
}

生成文件:

CFLAGS := -O3 -fopenmp
LDFLAGS := -O3 -lpthread  # just to be sure

all: add_seq add_omp add_pthread

所以,现在,运行它(使用zsh的内置时间):

% make -B && time ./add_seq && time ./add_omp && time ./add_pthread
cc -O3 -fopenmp  -O3 -lpthread    add_seq.c   -o add_seq
cc -O3 -fopenmp  -O3 -lpthread    add_omp.c   -o add_omp
cc -O3 -fopenmp  -O3 -lpthread    add_pthread.c   -o add_pthread
./add_seq  24.49s user 0.00s system 99% cpu 24.494 total
./add_omp  52.97s user 0.00s system 398% cpu 13.279 total
./add_pthread  52.92s user 0.00s system 398% cpu 13.266 total

检查CPU频率,顺序代码的最大CPU频率为2.90 GHz,并行代码(所有版本)具有2.60 GHz的统一CPU频率。计算数十亿条指令:

>>> 24.494 * 2.9
71.0326
>>> 13.279 * 2.6
34.5254
>>> 13.266 * 2.6
34.4916

所以,总而言之,线程代码的运行速度只是顺序代码的两倍,尽管它使用的CPU时间是其四倍。为什么会这样?

备注: asm_omp.c 的程序集似乎效率低,因为它通过递增寄存器并将其与迭代次数进行比较来执行for循环而不是递减和直接检查ZF;但是,这对性能没有影响

1 个答案:

答案 0 :(得分:3)

嗯,答案很简单:实际上只有两个CPU内核:

% lscpu
...
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
...

所以,尽管htop显示了4个CPU,但是有两个是虚拟的,只有hyperthreading。由于超线程的核心思想是在两个进程中共享单个核心的资源,因此它确实有助于更快地运行类似的代码(仅在使用不同资源运行两个线程时才有用)。

因此,最终,时间/ clock()测量每个逻辑核心的使用情况与底层物理核心的使用情况相同。由于所有报告约100%的使用率,我们得到约400%的使用率,尽管它只代表了两倍的加速。

直到那时,我确信这台计算机包含4个物理内核,并且完全忘记检查超线程。