尽管错误共享,但速度提高

Posted

技术标签:

【中文标题】尽管错误共享,但速度提高【英文标题】:Increased speed despite false sharing 【发布时间】:2015-06-08 09:07:13 【问题描述】:

我一直在对 OpenMP 进行一些测试,并且由于数组“sum”的错误共享而使该程序无法扩展。我遇到的问题是它确实可以扩展。甚至“更糟”:

1 个线程:4 秒 (icpc)、4 秒 (g++) 有 2 个线程:2 秒 (icpc)、2 秒 (g++) 4 线程:0.5 秒 (icpc)、1 秒 (g++)

我真的没有得到英特尔编译器从 2 个线程到 4 个线程的加速。但最重要的是:为什么扩展性如此好,即使它应该表现出虚假共享?

#include <iostream>
#include <chrono>

#include <array>

#include <omp.h>

int main(int argc, const char *argv[])

    const auto nb_threads = std::size_t4;
    omp_set_num_threads(nb_threads);

    const auto num_steps = std::size_t1000000000;
    const auto step = double1.0 / num_steps;
    auto sum = std::array<double, nb_threads>0.0;
    std::size_t actual_nb_threads;

    auto start_time = std::chrono::high_resolution_clock::now();
    #pragma omp parallel
    
        const auto id = std::size_tomp_get_thread_num();
        if (id == 0) 
            // This is needed because OMP might give us less threads
            // than the numbers of threads requested
            actual_nb_threads = omp_get_num_threads();
        
        for (auto i = std::size_t0; i < num_steps; i += nb_threads) 
            auto x = double(i + 0.5) * step;
            sum[id] += 4.0 / (1.0 + x * x);
        
    
    auto pi = double0.0;
    for (auto id = std::size_t0; id < actual_nb_threads; id++) 
        pi += step * sum[id];
    
    auto end_time = std::chrono::high_resolution_clock::now();
    auto time = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count();

    std::cout << "Pi: " << pi << std::endl;
    std::cout << "Time: " << time / 1.0e9 << " seconds" << std::endl;
    std::cout << "Total nb of threads actually used: " << actual_nb_threads << std::endl;

    return 0;

【问题讨论】:

修复虚假分享的速度有多快? 速度一模一样。 我不认为你在这里有虚假分享。每个线程只访问数组的一个专用元素。就好像每个线程都有自己的单个变量来存储总和。您不会在并发代码中迭代任何数组数据,因此没有什么可以以错误的方式共享。 Hanno:你应该在这里有错误的共享,因为 sum 的元素应该在同一个缓存行中,在我的计算机上是 64 B。 好的,我明白你的意思了。我虽然是关于从不同的缓存行访问数据。在这种情况下,@Sneftel 的回答很中肯。 【参考方案1】:

如果编译器选择以这种方式实现,那么该代码肯定可能表现出错误共享。但这对编译器来说是一件愚蠢的事情。

在第一个循环中,每个线程只访问sum 的一个元素。没有理由让num_steps 写入存储该元素的实际堆栈内存;将值保存在寄存器中并在 for 循环结束后将其写回要快得多。由于数组不是易失性或原子性的,因此没有什么可以阻止编译器以这种方式运行。

当然,在第二个循环中没有写入数组,所以没有错误共享。

【讨论】:

有道理。英特尔关于 OpenMP 的视频选择这个例子来解释虚假共享,并要求学生在他们的计算机上运行它。所以我希望他们的编译器不会“解决”这个问题。我还是不明白从 2 到 4 线程的加速! @InsideLoop 有点奇怪。不过,OpenMP 优化是如此不透明,我并不感到惊讶。 ICC 深处的一些晦涩的小调优启发式恰好适用于 4 个线程而不是 2 个或 1 个线程。

以上是关于尽管错误共享,但速度提高的主要内容,如果未能解决你的问题,请参考以下文章

尽管有多个错误,但 AJV 只返回一个错误

尽管只添加了一个指令,但多个指令错误

尽管已经声明了文本,但 TextView 出现空错误

尽管使用了 if 语句,但 RelatedObjectDoesNotExist 错误

尽管查询错误,但 Grafana 变量部分显示值的预览

尽管函数执行正确,但“将循环结构转换为 JSON”错误