缓慢的 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。如果两个线程同时执行此操作,您可能会损失其中一项。有两种简单的方法可以解决此问题:

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

  2. Return sum 并让 main 函数在调用 pthread_join().

    时读取它