备注:我觉得这有点愚蠢,但这可能对某人有帮助
所以,我试图通过使用并行性来提高程序的性能。但是,我遇到了测量加速的问题。我有 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,并行代码(所有版本)的统一CPU频率为2.60 GHz。算一下数十亿条指令:
>>> 24.494 * 2.9
71.0326
>>> 13.279 * 2.6
34.5254
>>> 13.266 * 2.6
34.4916
因此,总而言之,线程代码的运行速度仅是顺序代码的两倍,尽管它使用的 CPU 时间是顺序代码的四倍。为什么会这样?
备注:asm_omp.c
的汇编似乎效率较低,因为它通过递增寄存器并将其与迭代次数进行比较来执行for循环,而不是递减并直接检查ZF;然而,这对性能没有影响
% lscpu
...
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
...
因此,虽然 htop
显示了四个 CPU,但其中两个是虚拟的,并且只是因为超线程 才存在。由于超线程的核心思想是在两个进程中共享单个核心的资源,因此它不会使类似的代码更快(它仅在使用不同资源运行两个线程时有用)。 因此,最终发生的情况是 time/
clock() 测量每个逻辑核心的使用情况作为底层物理核心的使用情况。由于所有报告都报告 ~100% 使用率,我们得到 ~400% 使用率,尽管它只代表两倍的加速。
直到那时,我确信这台计算机包含 4 个物理核心,并且完全忘记检查超线程。