pthread 比 "default" 版本慢

pthread is slower than the "default" version

情况

我想看看使用 pthread 的优势。如果我没记错:线程允许我并行执行程序的给定部分。

所以这就是我试图完成的事情:我想制作一个程序,它接受一个数字(比如 n)并输出 [0..n].

的总和

代码

#define MAX 1000000000

int
main() {
    long long n = 0;
    for (long long i = 1; i < MAX; ++i)
        n += i;

    printf("\nn: %lld\n", n);
    return 0;
}

time: 0m2.723s

根据我的理解,我可以简单地取那个数字 MAX 除以 2 并让 2 threads 完成任务。

代码

#define MAX          1000000000
#define MAX_THREADS  2
#define STRIDE       MAX / MAX_THREADS

typedef struct {
    long long off;
    long long res;
} arg_t;

void*
callback(void *args) {
    arg_t *arg = (arg_t*)args;

    for (long long i = arg->off; i < arg->off + STRIDE; ++i)
        arg->res += i;

    pthread_exit(0);
}

int
main() {
    pthread_t threads[MAX_THREADS];
    arg_t     results[MAX_THREADS];

    for (int i = 0; i < MAX_THREADS; ++i) {
        results[i].off = i * STRIDE;
        results[i].res = 0;

        pthread_create(&threads[i], NULL, callback, (void*)&results[i]);
    }

    for (int i = 0; i < MAX_THREADS; ++i)
        pthread_join(threads[i], NULL);

    long long result;
    result = results[0].res;

    for (int i = 1; i < MAX_THREADS; ++i)
        result += results[i].res;

    printf("\nn: %lld\n", result);

    return 0;
}

time: 0m8.530s

问题

pthread运行的版本较慢。从逻辑上讲,这个版本应该 运行 更快,但也许创建线程的成本更高。

有人可以提出解决方案或说明我这里 doing/understanding 的错误吗?

创建一大堆线程不太可能比简单地添加数字更快。 CPU 可以在内核设置和拆除线程时添加大量整数。要看到多线程的好处,您确实需要每个线程都执行一项重要的任务——无论如何,与创建线程的开销相比,这是非常重要的。或者,您需要保留一个线程池 运行,并根据某种分配策略为它们分配工作。

Multi-threading 当应用程序包含有些独立的任务时效果最佳,否则这些任务将等待彼此完成。这不是获得更多吞吐量的神奇方法。

你的问题是缓存抖动加上缺乏优化(我敢打赌你在编译时没有启用它)。

的原始 (-O0) 代码
for (long long i = arg->off; i < arg->off + STRIDE; ++i)
    arg->res += i;

将访问 *arg 的内存。随着您的 results 数组按原样定义,该内存非常接近下一个 arg 的内存,并且两个线程将争夺相同的 cache-line,从而使 RAM 缓存非常无效。

如果您使用-O1 进行编译,则循环应该改用寄存器并且只在末尾写入内存。然后,你应该获得更好的线程性能(gcc 上更高的优化级别似乎完全优化了循环)

另一个(更好的)选项是在缓存行上对齐 arg_t

typedef struct {
    _Alignas(64) /*typical cache line size*/ long long off;
    long long res;
} arg_t;

然后无论是否打开优化,您都应该获得更好的线程性能。

良好的缓存利用率在多线程编程中通常非常重要(Ulrich Drepper 在他臭名昭著的 What Every Programmer Should Know About Memory 中对这个主题有很多话要说)。