- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
问题:
我正在寻找以线程安全的方式清除无符号原子(如std::atomic_uint64_t
)的最低非零位的最佳方法,而无需使用额外的互斥锁等。另外,我还需要知道哪些地方已经清除。
示例:
可以说,如果当前存储的值为0b0110
,我想知道最低的非零位是bit 1(0索引),并将变量设置为0b0100
。
我想出的最好的版本是this:
#include <atomic>
#include <cstdint>
inline uint64_t with_lowest_non_zero_cleared(std::uint64_t v){
return v-1 & v;
}
inline uint64_t only_keep_lowest_non_zero(std::uint64_t v){
return v & ~with_lowest_non_zero_cleared(v);
}
uint64_t pop_lowest_non_zero(std::atomic_uint64_t& foo)
{
auto expected = foo.load(std::memory_order_relaxed);
while(!foo.compare_exchange_weak(expected,with_lowest_non_zero_cleared(expected), std::memory_order_seq_cst, std::memory_order_relaxed))
{;}
return only_keep_lowest_non_zero(expected);
}
最佳答案
x86 上没有对原子清除最低位的直接硬件支持。 BMI1 blsr
仅以内存源形式提供,而不是内存目的地形式; lock blsr [shared_var]
不存在。 (当您使用(var-1) & (var)
进行编译或以其他方式启用假定BMI1支持的代码生成时,编译器知道如何针对本地var将blsr
优化为-march=haswell
。)因此,即使您可以假定BMI1 support1,也不会做任何根本不同的事情。
在x86上可以做的最好的事情就是在问题中建议的lock cmpxchg
重试循环。与在变量的旧版本中找到正确的位然后使用lock btr
相比,这是一个更好的选择,尤其是如果在bit-scan和lock btr
之间设置了较低的位以清除错误的位会是一个正确性问题时,尤其如此。而且,如果另一个线程已经清除了您想要的位,您仍然需要重试循环。
CAS重试循环在实践中还不错:重试非常少见
(除非您的程序对共享变量的争用非常高,即使使用lock add
进行尝试,只要尝试通过硬件仲裁来访问高速缓存行,即使使用expected
,性能也会出现问题。如果是这种情况,您可能应该重新设计无锁算法和/或考虑采用某种操作系统辅助的 sleep /唤醒方式,而不是让大量内核在同一缓存行上花费大量的CPU时间。在低争用情况下,无锁非常有用。)
CPU丢失在获取lock cmpxchg
的加载和运行cmpxchg
之间的高速缓存行的窗口很小,并且在该值上有几个指令的结果。 通常,它将第一次通过成功,因为当+=
运行时,高速缓存行仍将存在于L1d高速缓存中。当高速缓存行到达时,如果CPU能够看到足够远的距离为其执行RFO,则(希望)它已经处于MESI独占状态。
您可以检测cmpxchg重试循环,以查看实际程序中实际获得了多少竞争。 (例如,通过在循环内增加局部变量,并在成功后使用原子放松的expected
到共享计数器中,或与线程局部计数器一起使用)。
请记住,您的真实代码(希望如此)在此位掩码上的原子操作之间完成了大量工作,因此,它与微基准测试有很大不同,在微基准测试中,所有线程将所有时间都花在了该高速缓存行上。
EDIT: The update has to be atomic and global progress must be guaranteed, but just as with solution above, it doesn't have to be a wait free algorithm (of course I'd be very happy if you can show me one).
gcc8.1 -O3 -march=haswell
的线程将成功。希望在LL/SC机器上,一个内核上的SC故障不会引起其他内核上的SSC故障,否则它可能会死锁。您可以假设CPU架构师想到了这一点。
lock
编译到此asm(
from the Godbolt compiler explorer)
# gcc's code-gen for x86 with BMI1 looks optimal to me. No wasted instructions!
# presumably you'll get something similar when inlining.
pop_lowest_non_zero(std::atomic<unsigned long>&):
mov rax, QWORD PTR [rdi]
.L2:
blsr rdx, rax # Bit Lowest Set Reset
lock cmpxchg QWORD PTR [rdi], rdx
jne .L2 # fall through on success: cmpxchg sets ZF on equal like regular cmp
blsi rax, rax # Bit Lowest Set Isolate
ret
unlikely()
指令的成本,这与性能几乎无关。
lea rdx, [rax-1]
宏让clang生成不带分支的快速路径。
How do the likely() and unlikely() macros in the Linux kernel work and what is their benefit?) 。
and rdx, rax
/
blsr rdx, rax
之类的操作,而不是
lock cmpxchg
。这对于该功能而言是微不足道的。考虑到乱序执行吞吐量对周围代码的影响,即使在无竞争情况下,大部分成本也是原子操作,即使对于L1d高速缓存无争用情况也是如此。 (例如,在Skylake(
http://agner.org/optimize/)上为
blsr
设置10微秒,而不是使用
(x-1) & x
而不是其他2条指令来保存1 uop。假定前端是瓶颈,而不是内存或其他东西。内存不足会影响负载/也存储在周围的代码中,但幸运的是不会在无序执行独立ALU指令的情况下。)
fetch_add
而不是仅使用
compare_exchange_weak
获得的添加),但是使用CAS机器(例如x86)无法为您提供LL/SC提供的ABA检测。因此,目前尚不清楚如何设计C++类,该类可以在x86上有效地编译,但也可以直接在ARM和其他LL/SC ISA上直接编译为LL/SC重试循环。 (请参阅
this discussion。)
fetch_or
或
fetch_and
),则只需使用
compare_exchange_weak
编写代码。
ldaxr
,并且您的算术运算与此分开。实际上,asm会在排他性负载获取(
ldaxr
)之前执行宽松的负载,而不是仅仅基于
expected
结果进行计算。并且还有一个额外的分支,可以在尝试存储之前检查上次尝试的
sub
与新的加载结果是否匹配。
and
/
fetch_add
放入循环内,但取决于先前迭代的加载,因此在无序执行的情况下,它们仍可以提前准备就绪。
fetch_add
。他们没有,因为可能不是。 LL/SC重试很少,就像x86上的CAS重试一样。我不知道在竞争非常激烈的情况下它是否会有所作为。也许可以,但是编译器在编译
while()
时不会减慢为此优化的快速路径。
unlikely()
的可读性,因为一行已经太长而没有用
-O3
包裹它。
#include <atomic>
#include <cstdint>
#ifdef __GNUC__
#define unlikely(expr) __builtin_expect(!!(expr), 0)
#define likely(expr) __builtin_expect(!!(expr), 1)
#else
#define unlikely(expr) (expr)
#define likely(expr) (expr)
#endif
inline uint64_t clear_lowest_set(std::uint64_t v){
return v-1 & v;
}
inline uint64_t isolate_lowest_set(std::uint64_t v){
//return v & ~clear_lowest_set(v);
return (-v) & v;
// MSVC optimizes this better for ARM when used separately.
// The other way (in terms of clear_lowest_set) does still CSE to 2 instructions
// when the clear_lowest_set result is already available
}
uint64_t pop_lowest_non_zero(std::atomic_uint64_t& foo)
{
auto expected = foo.load(std::memory_order_relaxed);
while(unlikely(!foo.compare_exchange_weak(
expected, clear_lowest_set(expected),
std::memory_order_seq_cst, std::memory_order_relaxed)))
{}
return isolate_lowest_set(expected);
}
-target aarch64-linux-android -stdlib=libc++ -mcpu=cortex-a57
(Godbolt上的
dmb ish
)产生了一些奇怪的代码。这来自clang6.0,但是较旧的版本也很奇怪,在寄存器中创建一个0/1整数,然后对其进行测试,而不是仅仅跳到正确的位置。
pop_lowest_non_zero(std::__1::atomic<unsigned long long>&): // @pop_lowest_non_zero(std::__1::atomic<unsigned long long>&)
ldr x9, [x0] @ the relaxed load
ldaxr x8, [x0] @ the CAS load (a=acquire, x=exclusive: pairs with a later stxr)
cmp x8, x9 @ compare part of the CAS
b.ne .LBB0_3
sub x10, x9, #1
and x10, x10, x9 @ clear_lowest( relaxed load result)
stlxr w11, x10, [x0] @ the CAS store (sequential-release)
cbnz w11, .LBB0_4 @ if(SC failed) goto retry loop
# else fall through and eventually reach the epilogue
# this looks insane. w10 = 0|1, then branch if bit[0] != 0. Always taken, right?
orr w10, wzr, #0x1
tbnz w10, #0, .LBB0_5 @ test-bit not-zero will always jump to the epilogue
b .LBB0_6 # never reached
.LBB0_3:
clrex @ clear the ldrx/stxr transaction
.LBB0_4:
# This block is pointless; just should b to .LBB0_6
mov w10, wzr
tbz w10, #0, .LBB0_6 # go to the retry loop, below the ret (not shown here)
.LBB0_5: # isolate_lowest_set and return
neg x8, x9
and x0, x9, x8
ret
.LBB0_6:
@ the retry loop. Only reached if the compare or SC failed.
...
stlxr
之后仍然使用
compare_exchange_weak
指令(完整的内存屏障)。它具有更少的浪费指令,但是却显示出x86偏差:
compare_exchange_strong
像
dmb
一样编译,带有可能没有用的重试循环,它将在LL/SC失败时以旧的预期/期望再次尝试CAS。这通常是因为另一个线程修改了该行,因此预期会不匹配。 (Godbolt在任何较旧的版本中都没有针对AArch64的MSVC,因此也许是全新的支持。这可能解释了
expected
)
## MSVC Pre 2018 -Ox
|pop_lowest_non_zero| PROC
ldr x10,[x0] # x10 = expected = foo.load(relaxed)
|$LL2@pop_lowest| @ L2 # top of the while() loop
sub x8,x10,#1
and x11,x8,x10 # clear_lowest(relaxed load result)
|$LN76@pop_lowest| @ LN76
ldaxr x8,[x0]
cmp x8,x10 # the compare part of CAS
bne |$LN77@pop_lowest|
stlxr w9,x11,[x0]
cbnz w9,|$LN76@pop_lowest| # retry just the CAS on LL/SC fail; this looks like compare_exchange_strong
# fall through on LL/SC success
|$LN77@pop_lowest| @ LN77
dmb ish # full memory barrier: slow
cmp x8,x10 # compare again, because apparently MSVC wants to share the `dmb` instruction between the CAS-fail and CAS-success paths.
beq |$LN75@pop_lowest| # if(expected matches) goto epilogue
mov x10,x8 # else update expected
b |$LL2@pop_lowest| # and goto the top of the while loop
|$LN75@pop_lowest| @ LN75 # function epilogue
neg x8,x10
and x0,x8,x10
ret
.fetch_add
存储到堆栈中。 (Godbolt没有更新的AArch64 gcc)。
pop_lowest_non_zero ## hand written based on clang
# clang could emit this even without turning CAS into LL/SC directly
ldr x9, [x0] @ x9 = expected = foo.load(relaxed)
.Lcas_retry:
ldaxr x8, [x0] @ x8 = the CAS load (a=acquire, x=exclusive: pairs with a later stxr)
cmp x8, x9 @ compare part of the CAS
b.ne .Lcas_fail
sub x10, x9, #1
and x10, x10, x9 @ clear_lowest (relaxed load result)
stlxr w11, x10, [x0] @ the CAS store (sequential-release)
cbnz w11, .Lllsc_fail
# LL/SC success: isolate_lowest_set and return
.Lepilogue:
neg x8, x9
and x0, x9, x8
ret
.Lcas_fail:
clrex @ clear the ldrx/stxr transaction
.Lllsc_fail:
mov x9, x8 @ update expected
b .Lcas_retry @ instead of duplicating the loop, jump back to the existing one with x9 = expected
fetch_add()
比较:
add #1
编写了不错的代码:
mov x8, x0
.LBB1_1:
ldxr x0, [x8] # relaxed exclusive load: I used mo_release
add x9, x0, #1
stlxr w10, x9, [x8]
cbnz w10, .LBB1_1 # retry if LL/SC failed
ret
sub x9, x8, #1
,我们想要
and x9, x9, x8
/
uint64_t(~0ULL) << (64-counter)
,所以我们只得到一个LL/SC重试循环。这样可以节省代码大小,但不会明显更快。在大多数情况下,甚至可能不是相当快的速度,特别是作为整个程序的一部分,而该程序没有用到疯狂的数量。
tmp=1ULL << counter; tmp ^= tmp-1;
的位图(仅适用于非零计数器)。或
xor eax,eax
(即x86
bts rax, rdi
/
blsmsk rax, rax
(rax = 1将位置0..63设置为位)/
pdep
(rax =将所有位设置为该位置)。嗯,对于mask = 0仍需要特殊情况,因为连续的位掩码有65种可能的状态:最高(或最低)位在64个位置之一,或者根本没有设置任何位。
(1ULL << counter) - 1
可以将连续的位分散到该模式中。再次使用
lock cmpxchg
获取N个连续的设置位,用于计数器<64。
lock cmpxchg
来减少cmpxchg故障,而不会减少争用。这实际上对这种情况没有帮助,因为看到相同整数的两个线程都将尝试清除同一位。但是,这可以减少在此操作与设置qword中其他位的操作之间的重试。当然,只有当qword的其他字节已更改时ojit_code成功不是正确性问题时,这才起作用。
关于c++ - 以原子方式清除无符号整数的最低非零位,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51346815/
我一直在阅读有关汇编函数的内容,但对于是使用进入和退出还是仅使用调用/返回指令来快速执行,我感到很困惑。一种方式快而另一种方式更小吗?例如,在不内联函数的情况下,在汇编中执行此操作的最快(stdcal
我正在处理一个元组列表,如下所示: res = [('stori', 'JJ'), ('man', 'NN'), ('unnatur', 'JJ'), ('feel', 'NN'), ('pig',
最近我一直在做很多网络或 IO 绑定(bind)操作,使用线程有助于加快代码速度。我注意到我一直在一遍又一遍地编写这样的代码: threads = [] for machine, user, data
假设我有一个名为 user_stats 的资源,其中包含用户拥有的帖子、评论、喜欢和关注者的数量。是否有一种 RESTful 方式只询问该统计数据的一部分(即,对于 user_stats/3,请告诉我
我有一个简单的 api,它的工作原理是这样的: 用户创建一个请求 ( POST /requests ) 另一个用户检索所有请求 ( GET /requests ) 然后向请求添加报价 ( POST /
考虑以下 CDK Python 中的示例(对于这个问题,不需要 AWS 知识,这应该对基本上任何构建器模式都有效,我只是在这个示例中使用 CDK,因为我使用这个库遇到了这个问题。): from aws
Scala 中管理对象池的首选方法是什么? 我需要单线程创建和删除大规模对象(不需要同步)。在 C++ 中,我使用了静态对象数组。 在 Scala 中处理它的惯用和有效方法是什么? 最佳答案 我会把它
我有一个带有一些内置方法的类。这是该类的抽象示例: class Foo: def __init__(self): self.a = 0 self.b = 0
返回和检查方法执行的 Pythonic 方式 我目前在 python 代码中使用 golang 编码风格,决定移动 pythonic 方式 例子: import sys from typing imp
我正在开发一个 RESTful API。其中一个 URL 允许调用者通过 id 请求特定人员的记录。 返回该 id 不存在的记录的常规值是什么?服务器是否应该发回一个空对象或者一个 404,或者其他什
我正在使用 pathlib.Path() 检查文件是否存在,并使用 rasterio 将其作为图像打开. filename = pathlib.Path("./my_file-name.tif") 但
我正在寻找一种 Pythonic 方式来从列表和字典创建嵌套字典。以下两个语句产生相同的结果: a = [3, 4] b = {'a': 1, 'b': 2} c = dict(zip(b, a))
我有一个正在操裁剪理设备的脚本。设备有时会发生物理故障,当它发生时,我想重置设备并继续执行脚本。我有这个: while True: do_device_control() device
做组合别名的最pythonic和正确的方法是什么? 这是一个假设的场景: class House: def cleanup(self, arg1, arg2, kwarg1=False):
我正在开发一个小型客户端服务器程序来收集订单。我想以“REST(ful)方式”来做到这一点。 我想做的是: 收集所有订单行(产品和数量)并将完整订单发送到服务器 目前我看到有两种选择: 将每个订单行发
我知道在 Groovy 中您可以使用字符串调用类/对象上的方法。例如: Foo."get"(1) /* or */ String meth = "get" Foo."$meth"(1) 有没有办法
在 ECMAScript6 中,您可以使用扩展运算符来解构这样的对象 const {a, ...rest} = obj; 它将 obj 浅拷贝到 rest,不带属性 a。 有没有一种干净的方法可以在
我有几个函数返回数字或None。我希望我的包装函数返回第一个不是 None 的结果。除了下面的方法之外,还有其他方法吗? def func1(): return None def func2(
假设我想设计一个 REST api 来讨论歌曲、专辑和艺术家(实际上我就是这样做的,就像我之前的 1312414 个人一样)。 歌曲资源始终与其所属专辑相关联。相反,专辑资源与其包含的所有歌曲相关联。
这是我认为必须经常出现的问题,但我一直无法找到一个好的解决方案。假设我有一个函数,它可以作为参数传递一个开放资源(如文件或数据库连接对象),或者需要自己创建一个。如果函数需要自己打开文件,最佳实践通常
我是一名优秀的程序员,十分优秀!