【问题标题】:c++ openmp false-sharing on aligned array examplec++ openmp 对对齐数组的错误共享示例
【发布时间】:2019-04-16 05:36:24
【问题描述】:

我想看看虚假分享的效果。为此,我尝试设计一个小型实验,但得到了意想不到的结果。

我有一个包含 100 m 个整数的数组。将其视为 m x n 矩阵。一个线程更改奇数索引行,其他线程更改偶数索引行。

实验A:列数为16,所以每行64字节,正好是我的cacheline大小。由于每个线程一次只处理 1 个缓存行,因此不应存在错误共享。因此,我预计会有大约 100% 的加速。

实验B:列数为8,每个线程一次改变32个字节,是cacheline的一半。例如,如果线程 1 处理第 33 行,则应从线程 0 传输数据,因为线程 1 已经处理了同一高速缓存行中的第 32 行。 (反之亦然,顺序无关紧要)。由于这种通信,加速应该很低。

#include <iostream>
#include <omp.h>

using namespace std;

int main(int argc, char** argv) {

    if(argc != 3) {
        cout << "Usage: " << argv[0] << " <iteration> <col_count>" << endl;
        return 1;
    }

    int thread_count = omp_get_max_threads();
    int iteration = atoi(argv[1]);
    int col_count = atoi(argv[2]);
    int arr_size = 100000000;

    int* A = (int*) aligned_alloc(16 * sizeof(int), arr_size * sizeof(int));

    int row_count = arr_size / col_count; 
    int row_count_per_thread = row_count / thread_count;

    #pragma omp parallel
    {
        int thread_id = omp_get_thread_num();

        long long total = 1ll * iteration * row_count_per_thread * col_count;
        printf("%lld\n", total);

        for(int t = 0; t < iteration; t++) {

            for(int i = 0; i < row_count_per_thread; i++) {

                int start = (i * thread_count + thread_id) * col_count;
                for(int j = start; j < start + col_count; j++) {


                    if(A[j] % 2 == 0)
                        A[j] += 3;
                    else
                        A[j] += 1;
                }
            }
        }
    }

    return 0;
}

我通过以下方式以不同的配置运行此代码:

time taskset -c 0-1 ./run 100 16

这是 100 次迭代的结果:

Thread      Column      Optimization        Time (secs)
_______________________________________________________
1           16          O3                  7.6
1           8           O3                  7.7
2           16          O3                  7.7
2           8           O3                  7.7

1           16          O0                  35.9
1           8           O0                  34.3
2           16          O0                  19.3
2           8           O0                  18.2

如您所见,虽然 O3 优化提供了最好的结果,但它们很奇怪,因为增加线程数并没有提高任何速度。对我来说,O0 优化结果更容易解释。

真正的问题:看看最后两行。对于这两种情况,我得到了几乎 %100 的加速,但是我预计实验 B 的执行时间应该要长得多,因为它存在错误共享问题。我的实验或理解有什么问题?

我用 g++ -std=c++11 -Wall -fopenmp -O0 -o run -Iinc $(SOURCE)g++ -std=c++11 -Wall -fopenmp -O3 -o run -Iinc $(SOURCE)

如果我的问题不清楚或需要更多详细信息,请告诉我。


更新:规格:

MemTotal:        8080796 kB
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              8
On-line CPU(s) list: 0-7
Thread(s) per core:  2
Core(s) per socket:  4
Socket(s):           1
NUMA node(s):        1
Vendor ID:           GenuineIntel
CPU family:          6
Model:               71
Model name:          Intel(R) Core(TM) i7-5700HQ CPU @ 2.70GHz
Stepping:            1
CPU MHz:             2622.241
CPU max MHz:         3500,0000
CPU min MHz:         800,0000
BogoMIPS:            5387.47
Virtualization:      VT-x
L1d cache:           32K
L1i cache:           32K
L2 cache:            256K
L3 cache:            6144K
NUMA node0 CPU(s):   0-7

更新 2: 我尝试了不同的 iteration_countarr_size 参数,以便数组适合 L2、L1 缓存,同时使元素更改的总数保持不变。但结果还是一样。

谢谢。

【问题讨论】:

  • 为什么在禁用优化的情况下进行测试?这意味着有很多开销掩盖了错误的共享延迟......
  • 请重复优化 - 任何没有优化的性能讨论都是没有意义的。像这样梳理 800 MB 的数据永远不会超过 0.1 秒。也请将您的代码升级到minimal reproducible example 以提供实用的答案。
  • @MaxLanghof 感谢您的回复。我编辑了这个问题,但是当我通过 O3 优化增加线程数时,我没有得到任何加速。你能检查一下编辑过的问题吗,我添加了一个更简单的代码版本。
  • 你看过this video吗?这似乎是一个精确的代码副本。答案在视频中。
  • @Ripi2 在你提到它之后我已经检查了视频。谢谢,我认为这是一个非常好的资源,我从视频中学到了很多东西。虽然代码不一样,但概念是相似的。但是,我在这里所经历的与视频中应该发生的相反。我在问为什么会这样。

标签: c++ multithreading caching memory openmp


【解决方案1】:

您的 -O3 时间似乎与 your processor 的 1 通道内存访问速度一致。使用 2 通道内存配置可能会提高 2 倍的速度,但不太可能对结果产生任何其他差异。请记住,在您的处理器上,内核之间共享一个 L3 缓存,因此任何错误共享都极有可能在 L3 缓存级别上得到解决,并且不会导致外部内存总线上的额外负载。

您的代码存在更多问题(不仅仅是“缓慢”的内存访问),这些问题可能会阻止您看到错误共享的影响。

首先,考虑到线程调度中涉及的时间不可预测性,您的两个线程不太可能竞争完全相同的缓存行。

其次,即使他们确实有冲突,这也是暂时的,因为任何导致不对称减速的因素都会导致“较慢”的线程延迟其扫描,直到超出内存冲突范围。

第三,如果它们碰巧运行在同核的两个硬件线程上,它们会访问完全相同的缓存实例,不会发生缓存冲突。

要“修复”所有这些,您需要更多线程(或绑定到特定内核的线程)和更紧凑的内存区域以应对可能的冲突。 “最好”的结果是你的线程只竞争一个缓存行。

【讨论】:

  • 谢谢。在我所有的实验中,每个线程都绑定到不同的物理内核,所以我认为你的第三点不是问题。我将在内核数量更高的不同架构上使用不同数量的内核和不同的内存区域进行实验。当我得到结果时,我会更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
  • 2015-01-17
  • 2018-01-30
  • 2011-08-22
相关资源
最近更新 更多