【问题标题】:Slow pthreads, does not appear to be mere overhead缓慢的 pthreads,似乎不仅仅是开销
【发布时间】:2015-04-28 18:00:27
【问题描述】:

我一直在试图弄清楚为什么我的使用蒙特卡罗集成近似 pi 的程序在 pthreads 上运行得比单线程慢得多,两者都是用 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 秒,尽管这可能是由于线程被重用而不是创建的结果。这些结果适用于我的具有六个内核和超线程的桌面。在我的具有四核和超线程的笔记本电脑上,结果是相似的。

此外,我发现每个线程内完成的工作量增加了一倍,桌面上的执行时间增加了一倍多。然而,在我的笔记本电脑上,时间按预期缩放。也许这是由于架构的差异?艾维布里奇 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;
}

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

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

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

【问题讨论】:

  • 与性能问题无关,但在多线程版本中需要锁来保护共享变量sum(或使用原子增量)
  • 我也想过这个问题,但是如果两个线程同时尝试写入求和,这只会是一个问题,这不太可能,我没有打扰互斥锁。跨度>
  • '不太可能我没有打扰'......请永远不要在我曾经使用过的任何软件上工作 :) 说真的,这种对编码的态度会让你受伤。做对或花几个月的时间调试不应该发生的事情......
  • Concurrency bugs manifest only under specific interleavings...As a result, many concurrency bugs slip into production runs and manifest at user sites as failures. They have led to software failures that caused real-world disasters before.12 例如见Therac-25
  • 如果这是我投入生产的东西,我向你保证我会确保锁定我的变量,但这是一个玩具问题,除非我有线程数,否则它不会返回错误高得离谱。我怀疑这些问题是由于缺乏锁定引起的,因为随着线程越来越多,执行时间越短,问题的可能性就会增加。你是对的,迈克尔,这是一种可怕的态度。懒惰通常是不好的。如果这是为了一份工作,你可能会使用的东西,我不会偷工减料。

标签: c multithreading performance pthreads


【解决方案1】:

rand()is not reentrant or thread safe”。

您的线程可能会在 rand() 内部发生争执。

rand() 替换为rand_r()

【讨论】:

    【解决方案2】:

    拆分工作

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

    例如,使用 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()时读取它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-27
      • 2018-03-03
      • 2021-11-21
      相关资源
      最近更新 更多