使用多个线程时结果不同

时间:2018-11-21 20:10:39

标签: c++ vector openmp

以下代码执行转置矩阵矢量乘法,其中矩阵稀疏并以CSR格式存储。根据线程数,结果会有所不同。我认为原因是并发内存访问和添加。 有没有一种方法可以使用多线程,但保持与单线程相同的结果?

#pragma omp parallel for num_threads(m_numthreads)
    for (int i = 0; i < matrix.get_rowptr()->size() - 1; ++i)
    {
        for (int j = matrix.get_rowptr()->operator[](i); j < matrix.get_rowptr()->operator[](i + 1); ++j)
        {
            result[matrix.get_columnindex()->operator[](j)] += matrix.get_value()->operator[](j) * vector1[i];
        }
    }

2 个答案:

答案 0 :(得分:0)

确实应该使增量操作原子化,以确保两个线程不会同时更新值,这将是race condition。这将使代码正确,但是由于原子增量比标准增量慢,因此它可能使并行代码总体上慢于串行代码。
false-sharing也可能会影响性能:如果向量大小不大于线程数,则经常会发生两个线程尝试递增属于同一缓存行的值,并且花费大量时间来同步线程的情况。 CPU之间的缓存。

#pragma omp parallel for num_threads(m_numthreads)
    for (int i = 0; i < matrix.get_rowptr()->size() - 1; ++i)
        for (int j = matrix.get_rowptr()->operator[](i); j < matrix.get_rowptr()->operator[](i + 1); ++j)
        {
            #pragma omp atomic
            result[matrix.get_columnindex()->operator[](j)] += matrix.get_value()->operator[](j) * vector1[i];
        }

这就是说,对于结果向量中的给定元素,在串行和并行情况下,添加各种乘积以使该元素相加的顺序将是不同的。四舍五入的误差将有所不同,并且不应期望串行和并行结果相同,而是相对于 float double <的精度,这些结果之间的差异较小/ em>格式。

答案 1 :(得分:0)

没有可复制的示例,我不得不猜测以下代码的上下文:

#include <vector>

class SparseMatrix
{
    std::vector<double> values = { 5, 8, 3, 6, };
    std::vector<std::size_t> rows = { 0, 0, 2, 3, 4, };
    std::vector<std::size_t> cols = { 1, 2, 1, 1, };

public:
    const std::vector<std::size_t> *get_rowptr() const { return &rows; };
    const std::vector<std::size_t> *get_columnindex() const { return &cols; };
    const std::vector<double> *get_value() const { return &values; };
};

#include <array>
#include <iostream>
int main()
{
    SparseMatrix matrix;

    std::array<double, 4> result{};
    std::array<double, 4> vector1{ 1, 2, 3, 4 };

#pragma omp parallel for
    for (int i = 0; i < matrix.get_rowptr()->size() - 1; ++i)
    {
        for (int j = matrix.get_rowptr()->operator[](i); j < matrix.get_rowptr()->operator[](i + 1); ++j)
        {
            result[matrix.get_columnindex()->operator[](j)] += matrix.get_value()->operator[](j) * vector1[i];
        }
    }

    for (auto const& i: result)
        std::cout << i << " ";
    std::cout << '\n';
}

使用一些合适的变量,我们可以简化代码,因此我们可以看到发生了什么事情:

    auto const& rows = *matrix.get_rowptr();
    auto const& cols = *matrix.get_columnindex();
    auto const& values = *matrix.get_value();

 #pragma omp parallel for
    for (std::size_t i = 0;  i < rows.size() - 1;  ++i)
    {
        for (std::size_t j = rows[i];  j < rows[i+1];  ++j)
        {
            result[cols[j]] += values[j] * vector1[i];
        }
    }

现在,我们可以在循环的主体中看到分配给其他线程可能正在写入的结果条目的循环。我们需要序列化对result[cols[j]]的访问,以便一次仅一个线程正在执行+=。我们可以通过使用OMP atomic关键字将该操作标记为不可分割来实现:

#pragma omp parallel for
    for (std::size_t i = 0;  i < rows.size() - 1;  ++i)
    {
        for (std::size_t j = rows[i];  j < rows[i+1];  ++j)
        {
            auto& dest = result[cols[j]];
            auto const term = values[j] * vector1[i];
#pragma omp atomic
            dest += term;
        }
    }