我用标准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{}
而在通过指针访问值的上下文中没有任何内容。
答案 0 :(得分:0)
您可以创建std::vector<std::atomic<double>>
,但不能更改其大小。
我要做的第一件事是gsl::span
或写我自己的变体。然后,gsl::span<std::atomic<double>>
是values
比std::vector<std::atomic<double>>
更好的模型。
完成后,只需删除#pragma omp atomic
,您的代码在c++20中就是原子的。在c++17之前,您必须手动实施+=
。
double old = values[i];
while(!values[i].compare_exchange_weak(old, old+value))
{}
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 ++只允许您在原子数据上进行原子操作。