缓慢的pthreads,似乎不仅仅是开销

时间:2015-04-28 18:00:27

标签: c multithreading performance pthreads

我一直试图弄清楚为什么我的程序与蒙特卡洛集成近似pi的运行速度慢得多,而pthread则单线程用C语言编写。我在两台不同的机器上测试过这两台机器,它们都运行相同的操作系统但是不同的硬件,结果几乎相同。

首先是关于我的机器的一些信息:

$ 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)

桌面:Franken'puter(CPU:i7-4930k)

起初我遇到了C++ Pthreads - Multithreading slower than single-threading,其答案是线程的创建减慢了速度。这对我来说似乎不是问题。单线程程序需要3.57秒,6线程程序需要51秒,12线程程序需要1分6秒。如果创建线程是我唯一的问题,除了区别更大。此外,使用24个线程需要1分10秒,但这可能是因为线程被重用而不是创建。这些结果适用于我的桌面,它有六个核心和超线程。在我的具有四核和超线程的笔记本电脑上,结果是相似的。

此外,我发现每个线程内完成的工作量增加了一倍,超过了桌面上执行时间的两倍。但是在我的笔记本电脑上,时间按预期进行。也许这是由于建筑的差异? Ivybridge vs Haswell?

根据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;
}

我知道两者之间使用的采样点数量不同,因为我正在玩线程版试图弄清楚为什么它不能正常工作。然而,当我实际比较它们时,我确保线程数乘以每个线程计算的数字点数对于两者都是相同的。

[编辑]我在2周前进行初步搜索时没有看到这个帖子,也没有在我发布之前再次运行它,但似乎是完全相同的问题。我把它看到了我的一边。 Dividing work to more threads takes more time, why?

答案是rand()正在序列化线程,因为它们共享相同的种子,或类似的东西。所以它不是线程创建,而是rand()函数。我不确定这是不是答案,但我想我应该提一下。

2 个答案:

答案 0 :(得分:2)

rand()&#34; is not reentrant or thread safe&#34;。

您的主题可能会在rand()内部的某些内容上竞争。

rand_r()替换为{{1}}。

答案 1 :(得分:0)

分割工作

您的代码存在的最大问题是,您不能在线程之间拆分工作,您正在创建更多工作

例如,对于1个线程,您正在进行8000000次迭代。使用20个线程,每个线程执行8000000 。因此,如果您有4个内核,那么在完美条件下您可以期望的最好的是您的线程程序将比单线程程序长5倍。但你做了20次工作!

您需要做的是main()

long N = 6*100000000;

...

N /= M;  // Where M is the number of threads.

当我这样做时,我能够在单线程程序的1/4时间内运行线程程序(我有4个核心)。

随机数

第二个问题是您应该使用rand_r()而不是rand()。更改此操作可加快运行时间。但是,它会为您提供更好的结果,因为如果您使用rand(),您将在同时调用它的线程中获得重复的随机数。

安全存储金额

您不应该从每个帖子中添加sum。如果两个线程同时执行此操作,则可能会丢失一个总和。有两种简单的方法可以解决这个问题:

  1. sum设为大小为M的数组。然后将其索引传递给每个线程,并将其值存储到sum[index]

  2. 从线程函数返回sum,并在调用main时让pthread_join()函数读取它。