用c ++原子代替#pragma omp atomic

时间:2018-02-14 19:31:27

标签: c++11 c++14 openmp atomic compare-and-swap

我用标准C ++ 11 / C ++ 14原子/线程支持替换了一些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{}而在通过指针访问值的上下文中没有任何内容。

1 个答案:

答案 0 :(得分:0)

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

我要做的第一件事是gsl::span或写我自己的变体。然后,gsl::span<std::atomic<double>>valuesstd::vector<std::atomic<double>>更好的模型。

完成后,只需删除#pragma omp atomic,您的代码在中就是原子的。在之前,您必须手动实施+=

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 ++只允许您在原子数据上进行原子操作。