缓慢的 pthreads,似乎不仅仅是开销
Slow pthreads, does not appear to be mere overhead
我一直在试图弄清楚为什么我的使用 Monte Carlo 集成近似 pi 的程序 运行 使用 pthreads 比单线程慢得多,都是用 C 编写的。我已经在两个上测试了这个不同的机器,两者 运行 相同 OS 但硬件不同,结果几乎相同。
首先是关于我的机器的一些信息:
$ uname -rv
3.19.3-3-ARCH #1 SMP PREEMPT Wed Apr 8 14:10:00 CEST 2015
$ gcc --version
gcc (GCC) 4.9.2 20150304 (prerelease)
$ pacman -Q |grep gcc
gcc-fortran 4.9.2-4
gcc-libs-multilib 4.9.2-4
gcc-multilib 4.9.2-4
lib32-gcc-libs 4.9.2-4
笔记本电脑:Sager NP7358(CPU:i7-4710)
台式机:F运行电脑(CPU:i7-4930k)
起初我发现 C++ Pthreads - Multithreading slower than single-threading 答案是创建线程会减慢速度。这对我来说似乎不是问题。单线程程序耗时 3.57 秒,6 线程程序耗时 51 秒,12 线程程序耗时 1 分 6 秒。如果创建线程是唯一的问题,我会认为差异更大。此外,对于 24 个线程,它需要 1 分 10 秒,尽管这可能是线程被重用而不是创建的结果。这些结果适用于我的具有六个内核和超线程的桌面。在我的四核和超线程笔记本电脑上,结果是相似的。
此外,我发现每个线程内完成的工作量增加一倍,桌面上的执行时间就会增加一倍以上。然而,在我的笔记本电脑上,时间尺度符合预期。也许这是由于体系结构的差异? Ivybridge vs 哈斯韦尔?
根据 Htop,正在使用正确数量的逻辑核心,并且它们已达到最大值。
我正在用 "gcc -o mcpi_pthread mcpi_pthread.c -pthread" 编译所有线程代码,用 "gcc -o mcpi_nothread mcpi_nothread.c" 编译所有单线程代码。您会看到变量 n 和 M。我拥有这两个变量的原因是起初我不确定它们是否需要相等。事实证明他们这样做了,或者代码段错误。
首先是线程版本。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
int sum=0;
double frand() //why do I need this?
{
double RandomDouble = (double) rand()/RAND_MAX;
return RandomDouble;
}
int sample ()
/* This program is meant to generate a random x and a random y and check if
* $sqrt{1-x^2}<y$ */
{
double x = frand();
double y = frand();
if( y*y + x*x > 1 )
{
return 0;
}
else
{
return 1;
}
}
void *mcpi_routine(void *args); /*declare the routine, even if you
*/ don't define it
int main ()
/* Now we loop over N sample points to count how many times sample()
* comes up 1 then divide by N to get an approximation of pi/4
*/
{
srand(time(NULL));
long N =8000000 ,M=8 ,n=8;
double pi;
long i;
pthread_t threads[n]; //these are our threads
for(i=0;i<M;i++)
{
pthread_create(&threads[i],NULL,mcpi_routine,(void *) &N);
}
for(i=0;i<M;i++) pthread_join(threads[i],NULL);
pi = (double) 4.0 * sum/ (M*N);
printf("Pi is aproximately equal to %f.4 .\n",pi);
return 0;
}
void *mcpi_routine (void *args ) //need to create a routine
{
int c=0,i;
long *N = (long*) args;
for(i=0;i<*N;i++)
{
c += sample();
}
sum += c;
return 0;
}
现在单线程
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
//int RAND_MAX = pow(2,16)-1;
double frand() //why do I need this?
{
double RandomDouble = (double) rand()/RAND_MAX;
return RandomDouble;
}
//double frand();
int sample ()
/* This program is meant to generate a random x and a random y and check if
* $sqrt{1-x^2}<y$ */
{
// srand(time(NULL));
double x = frand();
double y = frand();
if( y*y + x*x > 1 )
{
return 0;
}
else
{
return 1;
}
}
main ()
/* Now we loop over N sample points to count how many times sample() comes up 1
* then divide by N to get an aproximation of pi/4 */
{
srand(time(NULL));
int count=0,i;
long N = 6*100000000;
double pi;
for(i=0;i<N;i++)
{
count += sample();
}
pi = 4.0 * count / N;
printf("Pi is aproximately equal to %f.4 .\n",pi);
return 0;
}
我知道两者使用的采样点数量不同,因为我一直在玩线程版本,试图弄清楚为什么它不能正常工作。然而,当我实际比较它们时,我确保线程数乘以每个线程计算的点数对于两者是相同的。
[edit] 我在 2 周前进行初始搜索时没有看到此线程,当我再次 运行 时也没有看到它,但它似乎是完全相同的问题。我看到它在我的线程的一侧。 Dividing work to more threads takes more time, why?
答案是 运行d() 正在序列化线程,因为它们共享相同的种子或类似的东西。所以它不是线程创建,而是 运行d() 函数。我不确定这是否是答案,但我想我应该提一下。
rand()
"is not reentrant or thread safe".
您的讨论帖可能正在争夺 rand()
.
的内部内容
将 rand()
替换为 rand_r()
。
拆分工作
您的代码最大的问题是您没有在线程之间拆分工作,您正在创建更多工作。
例如,对于 1 个线程,您将执行 8000000 次迭代。使用 20 个线程,每个线程 执行 8000000 。因此,如果您有 4 个内核,那么在完美条件下,您所能期望的最好结果是您的线程程序比单线程程序花费的时间长 5 倍。但是你做了 20 倍的工作!
你需要做的是在你的 main()
:
long N = 6*100000000;
...
N /= M; // Where M is the number of threads.
当我这样做时,我能够 运行 线程程序的时间是单线程程序(我有 4 个内核)的 1/4。
随机数
第二个问题是你应该使用rand_r()
而不是rand()
。更改此项 不会 加快 运行ning 时间。然而,它会给你更好的结果,因为如果你使用 rand()
,你将在同时调用它的线程中获得重复的随机数。
安全存储总和
您不应该从每个线程添加到 sum
。如果两个线程同时执行此操作,您可能会损失其中一项。有两种简单的方法可以解决此问题:
使 sum
成为大小为 M
的数组。然后将其索引传递给每个线程,并将其值存储到 sum[index]
.
Return sum
并让 main
函数在调用 pthread_join()
.
时读取它
我一直在试图弄清楚为什么我的使用 Monte Carlo 集成近似 pi 的程序 运行 使用 pthreads 比单线程慢得多,都是用 C 编写的。我已经在两个上测试了这个不同的机器,两者 运行 相同 OS 但硬件不同,结果几乎相同。
首先是关于我的机器的一些信息:
$ uname -rv
3.19.3-3-ARCH #1 SMP PREEMPT Wed Apr 8 14:10:00 CEST 2015
$ gcc --version
gcc (GCC) 4.9.2 20150304 (prerelease)
$ pacman -Q |grep gcc
gcc-fortran 4.9.2-4
gcc-libs-multilib 4.9.2-4
gcc-multilib 4.9.2-4
lib32-gcc-libs 4.9.2-4
笔记本电脑:Sager NP7358(CPU:i7-4710)
台式机:F运行电脑(CPU:i7-4930k)
起初我发现 C++ Pthreads - Multithreading slower than single-threading 答案是创建线程会减慢速度。这对我来说似乎不是问题。单线程程序耗时 3.57 秒,6 线程程序耗时 51 秒,12 线程程序耗时 1 分 6 秒。如果创建线程是唯一的问题,我会认为差异更大。此外,对于 24 个线程,它需要 1 分 10 秒,尽管这可能是线程被重用而不是创建的结果。这些结果适用于我的具有六个内核和超线程的桌面。在我的四核和超线程笔记本电脑上,结果是相似的。
此外,我发现每个线程内完成的工作量增加一倍,桌面上的执行时间就会增加一倍以上。然而,在我的笔记本电脑上,时间尺度符合预期。也许这是由于体系结构的差异? Ivybridge vs 哈斯韦尔?
根据 Htop,正在使用正确数量的逻辑核心,并且它们已达到最大值。
我正在用 "gcc -o mcpi_pthread mcpi_pthread.c -pthread" 编译所有线程代码,用 "gcc -o mcpi_nothread mcpi_nothread.c" 编译所有单线程代码。您会看到变量 n 和 M。我拥有这两个变量的原因是起初我不确定它们是否需要相等。事实证明他们这样做了,或者代码段错误。
首先是线程版本。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
int sum=0;
double frand() //why do I need this?
{
double RandomDouble = (double) rand()/RAND_MAX;
return RandomDouble;
}
int sample ()
/* This program is meant to generate a random x and a random y and check if
* $sqrt{1-x^2}<y$ */
{
double x = frand();
double y = frand();
if( y*y + x*x > 1 )
{
return 0;
}
else
{
return 1;
}
}
void *mcpi_routine(void *args); /*declare the routine, even if you
*/ don't define it
int main ()
/* Now we loop over N sample points to count how many times sample()
* comes up 1 then divide by N to get an approximation of pi/4
*/
{
srand(time(NULL));
long N =8000000 ,M=8 ,n=8;
double pi;
long i;
pthread_t threads[n]; //these are our threads
for(i=0;i<M;i++)
{
pthread_create(&threads[i],NULL,mcpi_routine,(void *) &N);
}
for(i=0;i<M;i++) pthread_join(threads[i],NULL);
pi = (double) 4.0 * sum/ (M*N);
printf("Pi is aproximately equal to %f.4 .\n",pi);
return 0;
}
void *mcpi_routine (void *args ) //need to create a routine
{
int c=0,i;
long *N = (long*) args;
for(i=0;i<*N;i++)
{
c += sample();
}
sum += c;
return 0;
}
现在单线程
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
//int RAND_MAX = pow(2,16)-1;
double frand() //why do I need this?
{
double RandomDouble = (double) rand()/RAND_MAX;
return RandomDouble;
}
//double frand();
int sample ()
/* This program is meant to generate a random x and a random y and check if
* $sqrt{1-x^2}<y$ */
{
// srand(time(NULL));
double x = frand();
double y = frand();
if( y*y + x*x > 1 )
{
return 0;
}
else
{
return 1;
}
}
main ()
/* Now we loop over N sample points to count how many times sample() comes up 1
* then divide by N to get an aproximation of pi/4 */
{
srand(time(NULL));
int count=0,i;
long N = 6*100000000;
double pi;
for(i=0;i<N;i++)
{
count += sample();
}
pi = 4.0 * count / N;
printf("Pi is aproximately equal to %f.4 .\n",pi);
return 0;
}
我知道两者使用的采样点数量不同,因为我一直在玩线程版本,试图弄清楚为什么它不能正常工作。然而,当我实际比较它们时,我确保线程数乘以每个线程计算的点数对于两者是相同的。
[edit] 我在 2 周前进行初始搜索时没有看到此线程,当我再次 运行 时也没有看到它,但它似乎是完全相同的问题。我看到它在我的线程的一侧。 Dividing work to more threads takes more time, why?
答案是 运行d() 正在序列化线程,因为它们共享相同的种子或类似的东西。所以它不是线程创建,而是 运行d() 函数。我不确定这是否是答案,但我想我应该提一下。
rand()
"is not reentrant or thread safe".
您的讨论帖可能正在争夺 rand()
.
将 rand()
替换为 rand_r()
。
拆分工作
您的代码最大的问题是您没有在线程之间拆分工作,您正在创建更多工作。
例如,对于 1 个线程,您将执行 8000000 次迭代。使用 20 个线程,每个线程 执行 8000000 。因此,如果您有 4 个内核,那么在完美条件下,您所能期望的最好结果是您的线程程序比单线程程序花费的时间长 5 倍。但是你做了 20 倍的工作!
你需要做的是在你的 main()
:
long N = 6*100000000;
...
N /= M; // Where M is the number of threads.
当我这样做时,我能够 运行 线程程序的时间是单线程程序(我有 4 个内核)的 1/4。
随机数
第二个问题是你应该使用rand_r()
而不是rand()
。更改此项 不会 加快 运行ning 时间。然而,它会给你更好的结果,因为如果你使用 rand()
,你将在同时调用它的线程中获得重复的随机数。
安全存储总和
您不应该从每个线程添加到 sum
。如果两个线程同时执行此操作,您可能会损失其中一项。有两种简单的方法可以解决此问题:
使
sum
成为大小为M
的数组。然后将其索引传递给每个线程,并将其值存储到sum[index]
.Return
时读取它sum
并让main
函数在调用pthread_join()
.