【问题标题】:False sharing in multi threads多线程中的虚假共享
【发布时间】:2020-05-06 10:01:01
【问题描述】:

随着我增加 NTHREADS,以下代码运行速度变慢。为什么使用更多的线程会使程序运行得更慢?有什么办法可以解决吗?有人说这是关于虚假分享,但我不太明白这个概念。

程序基本计算1到100000000的和。使用多线程的思想是将数字列表分成几个块,并行计算每个块的和,以使计算更快。

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

#define LENGTH 100000000
#define NTHREADS 2
#define NREPEATS 10
#define CHUNCK (LENGTH / NTHREADS)

typedef struct {
        size_t id;
        long *array;
        long result;
} worker_args;

void *worker(void *args) {

        worker_args *wargs = (worker_args*) args;

        const size_t start = wargs->id * CHUNCK;
        const size_t end = wargs->id == NTHREADS - 1 ? LENGTH : (wargs->id+1) * CHUNCK;

        for (size_t i = start; i < end; ++i) {
                wargs->result += wargs->array[i];
        }

        return NULL;
}

int main(void) {

        long* numbers = malloc(sizeof(long) * LENGTH);
        for (size_t i = 0; i < LENGTH; ++i) {
                numbers[i] = i + 1;
        }

        worker_args *args = malloc(sizeof(worker_args) * NTHREADS);

            for (size_t i = 0; i < NTHREADS; ++i) {
                    args[i] = (worker_args) {
                            .id = i,
                            .array = numbers, 
                            .result = 0
                    };
            }

            pthread_t thread_ids[NTHREADS];

            for (size_t i = 0; i < NTHREADS; ++i) {
                    pthread_create(thread_ids+i, NULL, worker, args+i);
            }

            for (size_t i = 0; i < NTHREADS; ++i) {
                    pthread_join(thread_ids[i], NULL);
            }

            long sum = 0;

            for (size_t i = 0; i < NTHREADS; ++i) {
                sum += args[i].result;
            }

            printf("Run %2zu: total sum is %ld\n", n, sum);

        free(args);
        free(numbers);
}

【问题讨论】:

  • 100000000*100000001/2 有什么问题 ;-)
  • 这只是为了练习使用多线程。 @4386427

标签: c multithreading pthreads


【解决方案1】:

为什么使用更多的线程会使程序运行更慢?

创建和加入线程会产生开销。如果线程没有太多工作要做,那么这种开销可能比实际工作更昂贵。

您的线程只是做一个简单的求和,并不那么昂贵。还要考虑从例如10 到 11 个线程不会对每个线程的工作负载产生太大影响。

10 个线程 --> 每个线程 10000000 个总和

11 个线程 --> 每个线程 9090909 个总和

创建额外线程的开销可能会超过每个线程“节省的工作量”。

在我的 PC 上,程序运行时间不到 100 毫秒。多线程不值得麻烦。

在多线程值得做之前,您需要处理更密集的任务。

另外请注意,创建比计算机拥有的内核数(包括超线程)更多的线程很少有意义。

虚假分享

是的,“错误共享”会影响多线程程序的性能,但我怀疑这是否是您的实际问题。

“错误共享”是在(某些)缓存系统中发生的事情,当两个线程(或者更确切地说是两个内核)写入属于同一缓存行的两个不同变量时。在这种情况下,两个线程/内核竞争拥有缓存线(用于写入),因此,它们必须一次又一次地刷新内存和缓存。这对性能不利。

正如我所说 - 我怀疑这是你的问题。一个聪明的编译器将只使用 CPU 寄存器来完成你的循环,并且只在最后写入内存。你可以检查你的代码的反汇编,看看是不是这样。

您可以通过增加结构的 sizeof 来避免“错误共享”,以使每个结构都适合您系统上缓存行的大小。

【讨论】:

  • 感谢您的回答!但是虚假分享会不会有问题? en.wikipedia.org/wiki/False_sharing
  • 编译器需要进行非常复杂的过程间分析和优化,以识别线程函数可以避免从内存中读取加数。这不太可能发生。但是,我同意不太可能存在错误共享问题,尤其是对于加数而言,因为它们仅由主线程编写,在它启动任何其他线程之前。读取不会使缓存失效。
  • @JohnBollinger 重新编译器优化:因为读取不是错误共享的问题我没有提到阅读wargs-&gt;array[i]。我指的是循环内wargs-&gt;result的写法。我编译可以在没有任何内存写入的情况下完成循环中的所有添加(即将运行结果保存在寄存器中),从而避免错误共享。
猜你喜欢
  • 2019-03-07
  • 1970-01-01
  • 2012-01-10
  • 2012-06-17
  • 1970-01-01
  • 2014-03-14
  • 2015-07-01
  • 2016-09-27
  • 1970-01-01
相关资源
最近更新 更多