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 中对这个主题有很多话要说)。
情况
我想看看使用 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 中对这个主题有很多话要说)。