用 C++ 原子替换 #pragma omp atomic

Replacing #pragma omp atomic with c++ atomics

我正在用标准 C++11/C++14 atomics/thread 支持替换一些 OpenMP 代码。这是 OpenMP 最小代码示例:

#include <vector>
#include <cstdint>

void omp_atomic_add(std::vector<std::int64_t> const& rows,
                    std::vector<std::int64_t> const& cols,
                    std::vector<double>& values,
                    std::size_t const row,
                    std::size_t const col,
                    double const value)
{
    for (auto i = rows[row]; i < rows[row+1]; ++i)
    {
        if (cols[i] == col)
        {
            #pragma omp atomic
            values[i] += value;
            return;
        }
    }
}

该代码更新了 CSR 矩阵格式,并出现在科学计算的热路径中。从技术上讲,使用 std::mutex 是可行的,但是 values 向量可以包含数百万个元素,并且访问次数比它多很多次,因此 std::mutex 太重了。

检查程序集 https://godbolt.org/g/nPE9Dt,它似乎使用了 CAS(免责声明,我的原子和程序集知识非常有限,所以我的评论可能不正确):

  mov rax, qword ptr [rdi]
  mov rdi, qword ptr [rax + 8*rcx]
  mov rax, qword ptr [rax + 8*rcx + 8]
  cmp rdi, rax
  jge .LBB0_6
  mov rcx, qword ptr [rsi]
.LBB0_2: # =>This Inner Loop Header: Depth=1
  cmp qword ptr [rcx + 8*rdi], r8
  je .LBB0_3
  inc rdi
  cmp rdi, rax
  jl .LBB0_2
  jmp .LBB0_6
 #### Interesting stuff happens from here onwards
.LBB0_3:
  mov rcx, qword ptr [rdx]             # Load values pointer into register
  mov rax, qword ptr [rcx + 8*rdi]     # Offset to value[i]
.LBB0_4: # =>This Inner Loop Header: Depth=1
  movq xmm1, rax                       # Move value into floating point register
  addsd xmm1, xmm0                     # Add function arg to the value from the vector<double>
  movq rdx, xmm1                       # Move result to register
  lock                                 # x86 lock
  cmpxchg qword ptr [rcx + 8*rdi], rdx # Compare exchange on the value in the vector
  jne .LBB0_4                          # If failed, go back to the top and try again
.LBB0_6:
  ret

使用 C++ 原子能做到这一点吗?我看到的示例仅使用 std::atomic<double> value{} 而在通过指针访问值的上下文中没有任何内容。

您可以创建 std::vector<std::atomic<double>> 但不能更改其大小。

我要做的第一件事是获取 gsl::span 或编写我自己的变体。那么 gsl::span<std::atomic<double>>valuesstd::vector<std::atomic<double>> 更好的模型。

完成后,只需删除 #pragma omp atomic 并且您的代码在 . In 中是原子的,然后您必须手动实施 +=.

double old = values[i];
while(!values[i].compare_exchange_weak(old, old+value))
{}

Live example.

Clang 5 生成:

omp_atomic_add(std::vector<long, std::allocator<long> > const&, std::vector<long, std::allocator<long> > const&, std::vector<std::atomic<double>, std::allocator<std::atomic<double> > >&, unsigned long, unsigned long, double): # @omp_atomic_add(std::vector<long, std::allocator<long> > const&, std::vector<long, std::allocator<long> > const&, std::vector<std::atomic<double>, std::allocator<std::atomic<double> > >&, unsigned long, unsigned long, double)
  mov rax, qword ptr [rdi]
  mov rdi, qword ptr [rax + 8*rcx]
  mov rax, qword ptr [rax + 8*rcx + 8]
  cmp rdi, rax
  jge .LBB0_6
  mov rcx, qword ptr [rsi]
.LBB0_2: # =>This Inner Loop Header: Depth=1
  cmp qword ptr [rcx + 8*rdi], r8
  je .LBB0_3
  inc rdi
  cmp rdi, rax
  jl .LBB0_2
  jmp .LBB0_6
.LBB0_3:
  mov rax, qword ptr [rdx]
  mov rax, qword ptr [rax + 8*rdi]
.LBB0_4: # =>This Inner Loop Header: Depth=1
  mov rcx, qword ptr [rdx]
  movq xmm1, rax
  addsd xmm1, xmm0
  movq rsi, xmm1
  lock
  cmpxchg qword ptr [rcx + 8*rdi], rsi
  jne .LBB0_4
.LBB0_6:
  ret

这和我不经意的一瞥一模一样。

atomic_view 有一项提案允许您通过原子视图操作非原子值。通常,C++ 只允许您对原子数据进行原子操作。