c++ - 尽管有虚假共享,但速度提高了

标签 c++ multithreading openmp false-sharing

我一直在对 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_t{4};
    omp_set_num_threads(nb_threads);

    const auto num_steps = std::size_t{1000000000};
    const auto step = double{1.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_t{omp_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_t{0}; i < num_steps; i += nb_threads) {
            auto x = double{(i + 0.5) * step};
            sum[id] += 4.0 / (1.0 + x * x);
        }
    }
    auto pi = double{0.0};
    for (auto id = std::size_t{0}; 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;
}

最佳答案

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

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

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

关于c++ - 尽管有虚假共享,但速度提高了,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30705112/

相关文章:

c++ - 如何在C++中对DynamoDB GetItem()启用ConsistentRead

c++ - C/C++ - 整数与指针冲突

java - 所有线程结束后打印数据

指向重写方法的 C++ 方法指针

多模式的 C++ 模板类实现

java - 通过 socket 包裹

multithreading - 用户级线程无锁编程的实用性

parallel-processing - OpenMP,使用所有内核并行

c++ - 如何为编译指示创建包装器并使其正确编译?

c - OpenMP - 没有错误共享的数组插入