C pthreads和drand48并发性能

C pthreads和drand48并发性能,c,pthreads,C,Pthreads,根据规范,C中的函数rand()使用互斥锁来锁定上下文()。所以,如果我使用多个线程调用它,我的程序会很慢,因为所有线程都会尝试访问这个锁定区域 因此,我找到了drand48(),另一个随机数生成器函数,它没有锁()。但是,不知何故,我的并行程序仍然比串行程序慢!代码粘贴在下面: 序列版本: #include <cstdlib> #define M 100000000 int main() { for (int i = 0; i < M; ++i)

根据规范,C中的函数rand()使用互斥锁来锁定上下文()。所以,如果我使用多个线程调用它,我的程序会很慢,因为所有线程都会尝试访问这个锁定区域

因此,我找到了drand48(),另一个随机数生成器函数,它没有锁()。但是,不知何故,我的并行程序仍然比串行程序慢!代码粘贴在下面:

序列版本:

#include <cstdlib>

#define M 100000000

int main()
{
    for (int i = 0; i < M; ++i)
        drand48(); 
    return 0;
}
#include <pthread.h>
#include <cstdlib>

#define M 100000000
#define N 4

pthread_t threads[N];

void* f(void* p)
{
    for (int i = 0; i < M/N; ++i)
        drand48();
}

int main()
{
    for (int i = 0; i < N; ++i)
            pthread_create(&threads[i], NULL, f, NULL);
    for (int i = 0; i < N; ++i)
            pthread_join(threads[i], NULL);
    return 0;
}
#包括
#定义M 100000000
int main()
{
对于(int i=0;i
并行版本:

#include <cstdlib>

#define M 100000000

int main()
{
    for (int i = 0; i < M; ++i)
        drand48(); 
    return 0;
}
#include <pthread.h>
#include <cstdlib>

#define M 100000000
#define N 4

pthread_t threads[N];

void* f(void* p)
{
    for (int i = 0; i < M/N; ++i)
        drand48();
}

int main()
{
    for (int i = 0; i < N; ++i)
            pthread_create(&threads[i], NULL, f, NULL);
    for (int i = 0; i < N; ++i)
            pthread_join(threads[i], NULL);
    return 0;
}
#包括
#包括
#定义M 100000000
#定义n4
pthread_t线程[N];
void*f(void*p)
{
对于(int i=0;i
我执行了两种代码。串行运行约0.6秒,并行运行约2.1秒

谁能解释一下为什么会发生这种情况


一些附加信息:我的电脑上有4个内核。我使用

g++serial.cpp-o serial

并行使用

g++parallel.cpp-lpthread-o并行


编辑:

显然,每当我更新线程中的全局变量时,就会发生性能损失。在下面的示例中,x变量是全局变量(注意,在并行示例中,操作将是非线程安全的):

序列号:

#include <cstdlib>

#define M 1000000000

int x = 0;

int main()
{
    for (int i = 0; i < M; ++i)
        x = x + 10 - 10;
    return 0;
}
#包括
#定义百万
int x=0;
int main()
{
对于(int i=0;i
平行:

#include <pthread.h>
#include <cstdlib>

#define M 1000000000
#define N 4

pthread_t threads[N];
int x;

void* f(void* p)
{
    for (int i = 0; i < M/N; ++i)
        x = x + 10 - 10;
}

int main()
{
    for (int i = 0; i < N; ++i)
        pthread_create(&threads[i], NULL, f, NULL);
    for (int i = 0; i < N; ++i)
        pthread_join(threads[i], NULL);
    return 0;
}
#包括
#包括
#定义百万
#定义n4
pthread_t线程[N];
int x;
void*f(void*p)
{
对于(int i=0;i
请注意,drand48()使用全局结构变量\u libc\u drand48\u data
drand48()
使用全局结构变量
\u libc\u drand48\u data
,它将状态保持在那里(写入),因此是缓存线争用的根源,而缓存线争用很可能是性能下降的根源。这不是我最初怀疑并在评论中写道的
虚假共享
,而是真正的共享。drand48()的实现中没有锁定的原因有两个:

  • drand48()是“drand48()、lrand48()和mrand48()函数不需要是线程安全的。”
  • 如果两个线程碰巧同时访问它,并且它们对内存的写入是交错的,则不会造成任何伤害——数据结构没有损坏,毕竟它应该返回伪随机数据
  • 当一个线程正在初始化状态时,在使用drand48()时有一些微妙的考虑(竞争条件)

    请注意,下面在_drand48_中迭代它如何在全局变量中存储三个16位字,这是随机生成器保持其状态的地方,也是线程之间缓存线争用的来源

    xsubi[0] = result & 0xffff;
    xsubi[1] = (result >> 16) & 0xffff;
    xsubi[2] = (result >> 32) & 0xffff;
    
    源代码 您提供了我在下面提供的链接以供参考。问题是状态更新时缓存线争用

    #include <stdlib.h>
    
    /* Global state for non-reentrant functions.  Defined in drand48-iter.c.  */
    
    extern struct drand48_data __libc_drand48_data;
    
    double drand48(void)    
    {
        double result;
        erand48_r (__libc_drand48_data.__x, &__libc_drand48_data, &result);
        return result;
    }
    
    以及
    \uu drand48\u iterate
    的实现,在这里它会写回全局

    int
    __drand48_iterate (unsigned short int xsubi[3], struct drand48_data *buffer)
    {
        uint64_t X;
        uint64_t result;
    
        /* Initialize buffer, if not yet done.  */
        if (unlikely(!buffer->__init))
        {
            buffer->__a = 0x5deece66dull;
            buffer->__c = 0xb;
            buffer->__init = 1;
        }
    
        /* Do the real work.  We choose a data type which contains at least
           48 bits.  Because we compute the modulus it does not care how
           many bits really are computed.  */
    
        X = (uint64_t) xsubi[2] << 32 | (uint32_t) xsubi[1] << 16 | xsubi[0];
    
        result = X * buffer->__a + buffer->__c;
    
        xsubi[0] = result & 0xffff;
        xsubi[1] = (result >> 16) & 0xffff;
        xsubi[2] = (result >> 32) & 0xffff;
    
        return 0;
    }
    
    int
    __drand48_迭代(无符号短整型xsubi[3],结构drand48_数据*缓冲区)
    {
    uint64_t X;
    uint64_t结果;
    /*初始化缓冲区(如果尚未完成)*/
    如果(不太可能(!缓冲区->初始化))
    {
    缓冲区->\uuuu a=0x5deec66dull;
    缓冲区->\uuu c=0xb;
    缓冲区->初始化=1;
    }
    /*做真正的工作。我们选择一个至少包含
    48位。因为我们计算模,所以它不关心如何计算
    许多位实际上是经过计算的*/
    X=(uint64_t)xsubi[2]u c;
    xsubi[0]=结果&0xffff;
    xsubi[1]=(结果>>16)&0xffff;
    xsubi[2]=(结果>>32)&0xffff;
    返回0;
    }
    
    线程创建的开销很大,并行性带来的改进必须足够大才能保证开销。对于
    M=100000000
    您刚刚发现
    M
    太小(或者线程创建太慢)。事实并非如此。如果我将函数drand48()改为一个简单的算术函数,并行程序的运行速度会比串行程序快。。。那么有可能drand48()正在进行错误共享。。。也就是说,当两个线程共享一条缓存线时,尽管它们访问的数据不在同一地址,但至少有一个线程是写入程序-缓存线在内核之间来回乒乓。。如果缓存线的高争用性会影响性能(有时会非常严重),这是有道理的。但是,我怎样才能真正证明虚假分享真的发生了呢?