在 C 中使用相同参数生成 n 个 pthread 的最有效方法
Most efficient way to spawn n pthreads with the same parameters in C
我有 32 个线程,我提前知道输入参数,函数内部没有任何变化(每个线程与之交互的内存缓冲区除外)。
在伪 C 代码中,这是我的设计模式:
// declare 32 pthreads as global variables
void dispatch_32_threads() {
for(int i=0; i < 32; i++) {
pthread_create( &thread_id[i], NULL, thread_function, (void*) thread_params[i] );
}
// wait until all 32 threads are finished
for(int j=0; j < 32; j++) {
pthread_join( thread_id[j], NULL);
}
}
int main (crap) {
//init 32 pthreads here
for(int n = 0; n<4000; n++) {
for(int x = 0; x<100< x++) {
for(int y = 0; y<100< y++) {
dispatch_32_threads();
//modify buffers here
}
}
}
}
我打了 dispatch_32_threads
100*100*4000= 40000000
次电话。 thread_function
和 (void*) thread_params[i]
不变。我认为 pthread_create
一直在创建和销毁线程,我有 32 个内核,其中 none 的利用率为 100%,徘徊在 12% 左右。此外,当我将线程数减少到 10 个时,所有 32 个内核的利用率都保持在 5-7%,而且我没有看到运行时间变慢。 运行 不到 10 个慢下来。
运行 但是 1 个线程非常慢,所以多线程有帮助。我分析了我的代码,我知道 thread_func
很慢,而 thread_func
是可并行化的。这让我相信 pthread_create
不断在不同的内核上生成和销毁线程,在 10 个线程后我失去了效率,并且速度变慢了,thread_func
本质上 "less complicated" 比生成更多10 个线程。
这个评价是真的吗? 100% 利用所有内核的最佳方法是什么?
线程创建很昂贵。它取决于不同的参数,但很少低于 1000 次循环。和线程的同步和销毁类似。如果您 thread_function 中的工作量不是很大,它将在很大程度上控制计算时间。
在内部循环中创建线程很少是个好主意。可能最好的方法是创建线程来处理外循环的迭代。根据您的程序以及 thread_function
的作用,迭代之间可能存在依赖关系,这可能需要进行一些重写,但解决方案可能是:
int outer=4000;
int nthreads=32;
int perthread=outer/nthreads;
// add an integer with thread_id to thread_param struct
void thread_func(whatisrequired *thread_params){
// runs perthread iteration of the loop beginning at start
int start = thread_param->thread_id;
for(int n = start; n<start+perthread; n++) {
for(int x = 0; x<100< x++) {
for(int y = 0; y<100< y++) {
//do the work
}
}
}
}
int main(){
for(int i=0; i < 32; i++) {
thread_params[i]->thread_id=i;
pthread_create( &thread_id[i], NULL, thread_func,
(void*) thread_params[i]);
}
// wait until all 32 threads are finished
for(int j=0; j < 32; j++) {
pthread_join( thread_id[j], NULL);
}
}
这种并行化,可以考虑使用openmp。 parallel for
子句将使您轻松试验最佳并行化方案。
如果存在依赖关系并且不可能进行如此明显的并行化,您可以在程序启动时创建线程并通过管理 thread pool 来为它们提供工作。管理队列比线程创建更便宜(但原子访问确实有成本)。
编辑:或者,您可以
1.把你所有的循环都放在线程函数中
2. 在内部循环的开始(或结束)处添加一个 barrier 以同步您的线程。这将确保所有线程都已完成其工作。
3. 在main
中创建所有线程并等待完成。
障碍比线程创建更便宜,结果是相同的。
我有 32 个线程,我提前知道输入参数,函数内部没有任何变化(每个线程与之交互的内存缓冲区除外)。
在伪 C 代码中,这是我的设计模式:
// declare 32 pthreads as global variables
void dispatch_32_threads() {
for(int i=0; i < 32; i++) {
pthread_create( &thread_id[i], NULL, thread_function, (void*) thread_params[i] );
}
// wait until all 32 threads are finished
for(int j=0; j < 32; j++) {
pthread_join( thread_id[j], NULL);
}
}
int main (crap) {
//init 32 pthreads here
for(int n = 0; n<4000; n++) {
for(int x = 0; x<100< x++) {
for(int y = 0; y<100< y++) {
dispatch_32_threads();
//modify buffers here
}
}
}
}
我打了 dispatch_32_threads
100*100*4000= 40000000
次电话。 thread_function
和 (void*) thread_params[i]
不变。我认为 pthread_create
一直在创建和销毁线程,我有 32 个内核,其中 none 的利用率为 100%,徘徊在 12% 左右。此外,当我将线程数减少到 10 个时,所有 32 个内核的利用率都保持在 5-7%,而且我没有看到运行时间变慢。 运行 不到 10 个慢下来。
运行 但是 1 个线程非常慢,所以多线程有帮助。我分析了我的代码,我知道 thread_func
很慢,而 thread_func
是可并行化的。这让我相信 pthread_create
不断在不同的内核上生成和销毁线程,在 10 个线程后我失去了效率,并且速度变慢了,thread_func
本质上 "less complicated" 比生成更多10 个线程。
这个评价是真的吗? 100% 利用所有内核的最佳方法是什么?
线程创建很昂贵。它取决于不同的参数,但很少低于 1000 次循环。和线程的同步和销毁类似。如果您 thread_function 中的工作量不是很大,它将在很大程度上控制计算时间。
在内部循环中创建线程很少是个好主意。可能最好的方法是创建线程来处理外循环的迭代。根据您的程序以及 thread_function
的作用,迭代之间可能存在依赖关系,这可能需要进行一些重写,但解决方案可能是:
int outer=4000;
int nthreads=32;
int perthread=outer/nthreads;
// add an integer with thread_id to thread_param struct
void thread_func(whatisrequired *thread_params){
// runs perthread iteration of the loop beginning at start
int start = thread_param->thread_id;
for(int n = start; n<start+perthread; n++) {
for(int x = 0; x<100< x++) {
for(int y = 0; y<100< y++) {
//do the work
}
}
}
}
int main(){
for(int i=0; i < 32; i++) {
thread_params[i]->thread_id=i;
pthread_create( &thread_id[i], NULL, thread_func,
(void*) thread_params[i]);
}
// wait until all 32 threads are finished
for(int j=0; j < 32; j++) {
pthread_join( thread_id[j], NULL);
}
}
这种并行化,可以考虑使用openmp。 parallel for
子句将使您轻松试验最佳并行化方案。
如果存在依赖关系并且不可能进行如此明显的并行化,您可以在程序启动时创建线程并通过管理 thread pool 来为它们提供工作。管理队列比线程创建更便宜(但原子访问确实有成本)。
编辑:或者,您可以
1.把你所有的循环都放在线程函数中
2. 在内部循环的开始(或结束)处添加一个 barrier 以同步您的线程。这将确保所有线程都已完成其工作。
3. 在main
中创建所有线程并等待完成。
障碍比线程创建更便宜,结果是相同的。