【问题标题】:Multi-threaded random_r is slower than single threaded version多线程 random_r 比单线程版本慢
【发布时间】:2011-03-01 08:05:33
【问题描述】:

以下程序与here 描述的程序基本相同。当我使用两个线程 (NTHREADS == 2) 运行和编译程序时,我得到以下运行时间:

real        0m14.120s
user        0m25.570s
sys         0m0.050s

当它仅使用一个线程 (NTHREADS == 1) 运行时,即使它仅使用一个内核,我的运行时间也会显着提高。

real        0m4.705s
user        0m4.660s
sys         0m0.010s

我的系统是双核的,我知道 random_r 是线程安全的,我很确定它是非阻塞的。当相同的程序在没有 random_r 的情况下运行并使用余弦和正弦的计算作为替代时,双线程版本的运行时间约为预期的 1/2。

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

#define NTHREADS 2
#define PRNG_BUFSZ 8
#define ITERATIONS 1000000000

void* thread_run(void* arg) {
    int r1, i, totalIterations = ITERATIONS / NTHREADS;
    for (i = 0; i < totalIterations; i++){
        random_r((struct random_data*)arg, &r1);
    }
    printf("%i\n", r1);
}

int main(int argc, char** argv) {
    struct random_data* rand_states = (struct random_data*)calloc(NTHREADS, sizeof(struct random_data));
    char* rand_statebufs = (char*)calloc(NTHREADS, PRNG_BUFSZ);
    pthread_t* thread_ids;
    int t = 0;
    thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t));
    /* create threads */
    for (t = 0; t < NTHREADS; t++) {
        initstate_r(random(), &rand_statebufs[t], PRNG_BUFSZ, &rand_states[t]);
        pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t]);
    }
    for (t = 0; t < NTHREADS; t++) {
        pthread_join(thread_ids[t], NULL);
    }
    free(thread_ids);
    free(rand_states);
    free(rand_statebufs);
}

我很困惑为什么在生成随机数时两个线程版本的性能比单线程版本差得多,考虑到 random_r 旨在用于多线程应用程序。

【问题讨论】:

    标签: c linux performance multithreading random


    【解决方案1】:

    一个非常简单的改变,将内存中的数据隔开:

    struct random_data* rand_states = (struct random_data*)calloc(NTHREADS * 64, sizeof(struct random_data));
    char* rand_statebufs = (char*)calloc(NTHREADS*64, PRNG_BUFSZ);
    pthread_t* thread_ids;
    int t = 0;
    thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t));
    /* create threads */
    for (t = 0; t < NTHREADS; t++) {
        initstate_r(random(), &rand_statebufs[t*64], PRNG_BUFSZ, &rand_states[t*64]);
        pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t*64]);
    }
    

    在我的双核机器上运行时间更快。

    这将证实它旨在测试的怀疑 - 您正在两个单独的线程中对同一缓存行上的值进行变异,因此存在缓存争用。 Herb Sutter 的 'machine architecture - what your programming language never told you' talk 值得一看,如果你有时间,如果你还不知道的话,他在 1:20 左右开始演示虚假分享。

    计算出您的缓存行大小,并创建每个线程的数据以使其与其对齐。

    将所有线程的数据plonk到一个结构中并对齐它会更干净一点:

    #define CACHE_LINE_SIZE 64
    
    struct thread_data {
        struct random_data random_data;
        char statebuf[PRNG_BUFSZ];
        char padding[CACHE_LINE_SIZE - sizeof ( struct random_data )-PRNG_BUFSZ];
    };
    
    int main ( int argc, char** argv )
    {
        printf ( "%zd\n", sizeof ( struct thread_data ) );
    
        void* apointer;
    
        if ( posix_memalign ( &apointer, sizeof ( struct thread_data ), NTHREADS * sizeof ( struct thread_data ) ) )
            exit ( 1 );
    
        struct thread_data* thread_states = apointer;
    
        memset ( apointer, 0, NTHREADS * sizeof ( struct thread_data ) );
    
        pthread_t* thread_ids;
    
        int t = 0;
    
        thread_ids = ( pthread_t* ) calloc ( NTHREADS, sizeof ( pthread_t ) );
    
        /* create threads */
        for ( t = 0; t < NTHREADS; t++ ) {
            initstate_r ( random(), thread_states[t].statebuf, PRNG_BUFSZ, &thread_states[t].random_data );
            pthread_create ( &thread_ids[t], NULL, &thread_run, &thread_states[t].random_data );
        }
    
        for ( t = 0; t < NTHREADS; t++ ) {
            pthread_join ( thread_ids[t], NULL );
        }
    
        free ( thread_ids );
        free ( thread_states );
    }
    

    CACHE_LINE_SIZE64:

    refugio:$ gcc -O3 -o bin/nixuz_random_r src/nixuz_random_r.c -lpthread
    refugio:$ time bin/nixuz_random_r 
    64
    63499495
    944240966
    
    real    0m1.278s
    user    0m2.540s
    sys 0m0.000s
    

    或者您可以使用双倍缓存行大小,并使用 malloc - 额外的填充可确保变异内存位于不同的行上,因为 malloc 是 16 (IIRC) 而不是 64 字节对齐的。

    (我将迭代次数减少了十倍,而不是拥有一台速度极快的机器)

    【讨论】:

    • 呃。这几乎可以咬住多个线程将尝试写入的任何小而密集的结构,对吧?
    • 感谢一百万的帮助,我自己永远无法解决这个问题。附言。我将 rand_states 和 rand_statebufs 移到了线程中,并从那里初始化了随机数生成器。这也以非常简单的方式很好地解决了缓存问题。
    • @Nicholas:是的。不要对记忆过于刻薄是值得的。请注意,将线程本地分配打包在一起也会有所帮助。如果处理得当,线程局部变量可以是一个巨大的胜利,因为您可以避免如此多的缓存争用和锁定。
    • @Pete,我知道您发布此内容已经有好几年了,但您发布的视频的链接已失效。还有很多 Herb Sutter 视频,我想知道您是否记得您要链接的视频的名称。
    • @Kairos 我在 youtube 上找到并更新了答案。
    【解决方案2】:

    我不知道这是否相关——但我只是看到了一个非常相似的行为(2 个线程比一个线程慢一个数量级)......我基本上改变了:

      srand(seed);
      foo = rand();
    

    到一个

      myseed = seed;
      foo = rand_r(&myseed);
    

    并且“修复”了它(现在 2 个线程的速度几乎是可靠的两倍 - 例如 19 秒而不是 35 秒)。

    我不知道问题可能是什么——rand() 内部的锁定或缓存一致性可能吗?无论如何,还有一个random_r(),所以也许这对你(一年前)或其他人有用。

    【讨论】:

      猜你喜欢
      • 2012-09-05
      • 1970-01-01
      • 2020-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-21
      相关资源
      最近更新 更多