同时使用非原子操作和原子操作
Use non-atomic and atomic operations at the same time
我有一个线程池,每个线程都包含一个计数器(基本上是TLS)。
主线程需要通过计算所有线程本地计数器的总和来频繁更新。
大多数时候,每个线程都会增加自己的计数器,因此不需要同步。
但是在主线程更新的时候,我当然需要某种同步。
我提出了MSVS内部函数(_InterlockedXXX
函数),它显示出了很好的性能(在我的测试中大约0.8秒)然而,它将我的代码限制在MSVC编译器和X86/AMD64平台上,但有C++可移植的方法吗?
-
我尝试将计数器的int类型更改为
std::atomic<int>
,使用std::memory_order_relaxed
进行增量,但这个解决方案非常慢!(~4s) -
当使用基成员
std::atomic<T>::_My_val
时,可以像我希望的那样以非原子方式访问该值,但它也不可移植,所以问题是一样的。。。 -
由于高争用(~10s),使用由所有线程共享的单个
std::atomic<int>
甚至更慢
你有什么想法吗?也许我应该使用库(boost)?或者写我自己的课?
std::atomic<int>::fetch_add(1, std::memory_order_relaxed)
和_InterlockedIncrement
一样快。
Visual Studio将前者编译为lock add $1
(或等效版本),将后者编译为lock inc
,但执行时间没有差异;在我的系统上(酷睿i5@3.30 GHz),每个需要5630 ps/op,大约18.5个周期。
使用台式机的微型基准标记:
#define BENCHPRESS_CONFIG_MAIN
#include "benchpress/benchpress.hpp"
#include <atomic>
#include <intrin.h>
std::atomic<long> counter;
void f1(std::atomic<long>& counter) { counter.fetch_add(1, std::memory_order_relaxed); }
void f2(std::atomic<long>& counter) { _InterlockedIncrement((long*)&counter); }
BENCHMARK("fetch_add_1", [](benchpress::context* ctx) {
auto& c = counter; for (size_t i = 0; i < ctx->num_iterations(); ++i) { f1(c); }
})
BENCHMARK("intrin", [](benchpress::context* ctx) {
auto& c = counter; for (size_t i = 0; i < ctx->num_iterations(); ++i) { f2(c); }
})
输出:
fetch_add_1 200000000 5634 ps/op
intrin 200000000 5637 ps/op
我提出了这种适合我的实现。但是,我找不到编码semi_atomic<T>::Set()
的方法
#include <atomic>
template <class T>
class semi_atomic<T> {
T Val;
std::atomic<T> AtomicVal;
semi_atomic<T>() : Val(0), AtomicVal(0) {}
// Increment has no need for synchronization.
inline T Increment() {
return ++Val;
}
// Store the non-atomic Value atomically and return it.
inline T Get() {
AtomicVal.store(Val, std::memory_order::memory_order_release);
return AtomicVal.load(std::memory_order::memory_order_relaxed);
}
// Load _Val into Val, but in an atomic way (?)
inline void Set(T _Val) {
_InterlockedExchange((volatile long*)&Val, _Val); // And with C++11 ??
}
}
谢谢你,如果出了什么问题就告诉我!
您肯定是对的:每个线程需要一个std::atomic<int>
来实现可移植性,即使它在某种程度上很慢。
然而,在X86和AMD64体系结构的情况下,它可以(非常)优化。
这是我得到的,sInt
是一个有符号的32位或64位。
// Here's the magic
inline sInt MyInt::GetValue() {
return *(volatile sInt*)&Value;
}
// Interlocked intrinsic is atomic
inline void MyInt::SetValue(sInt _Value) {
#ifdef _M_IX86
_InterlockedExchange((volatile sInt *)&Value, _Value);
#else
_InterlockedExchange64((volatile sInt *)&Value, _Value);
#endif
}
此代码将在具有X86体系结构的MSVS中工作(GetValue()
需要)
- 对OpenMP reduction子句中的变量执行原子操作
- C++:将值赋值给原始数据类型(例如布尔值)是原子操作吗?
- 标记为 std::memory_order_seq_cst 的单个原子操作是否会在所有位置触发顺序一致性?
- 对单个变量的原子操作
- 对于 CPU 无法原子操作的类型,std::atomic 有什么意义?
- 为什么互斥对象与原子操作不同,前者是操作系统级,后者是处理器级
- 原子变量的多重赋值是原子操作吗?
- 对标准类型使用原子操作
- 共享指针,C 版本的原子操作
- 即使在单线程程序中,共享指针是否在引用计数中使用原子操作
- 原子操作传播/可见性(原子负载与原子RMW负载)
- 为什么原子操作需要独家的缓存访问
- 在英特尔上自然对齐的POD类型的保证原子操作
- C++ 如何编写原子操作
- 原子操作示例'Concurrency in Action'的正确性
- 嵌套原子操作是否保证为原子操作
- 如何实现原子操作
- 如何使用原子操作fetch_or或test_and_set在指针中设置一点,而在C 11中不使用Amberare_
- 指针地址交换总是C++中的原子操作吗
- 何时不使用原子操作