为什么 std::fetch_add returns 旧值?
Why std::fetch_add returns the old value?
是什么设计目的或技术限制使std::fetch_add
的return值变成了更改前的值?
这两种方式都没什么大不了的,你可以模仿另一种方式。例如如果需要,val.add_fetch(1)
可以用 1 + val.add_fetch(1)
实现。然而,GNU C __atomic
builtins provide both.
ISO C/C++ 仅提供 fetch_add
而不是 add_fetch
的可能原因:在某些情况下,它使得在 x86 上实现更便宜; lock xadd [mem], reg
留下 reg = mem 的旧值,mem = sum。提供原语而不是其他原语可以鼓励人们围绕该构建块设计算法,也许可以避免需要额外的 add
指令。
大多数具有 LL/SC 原子的 RISC ISA 有 3 个操作数指令,所以它们可以 add dst, src1, src2
并且如果代码稍后需要它,它们可以将内存中的值原封不动地留在另一个寄存器中。 (LL/SC fetch_add(x)
通常会实现为 load-linked reg1, [mem]
/ add reg2, reg1, x
/ store-conditional reg3, reg2, [mem]
。使用基于 reg3 中 success/fail 结果的重试循环。如果 fetch_add
return 值未使用,add
可以覆盖 reg1
而不是使用新的 reg.
所以在大多数 RISC 上,无论哪种方式都很好,x86 是更相关的 ISA 之一,需要关心效率。
对于某些用例,fetch_add
也是您想要的。例如对于在基于数组的循环缓冲区无锁队列中抓取桶的线程,std::atomic<unsigned> write_idx;
零初始化开始,您希望 .fetch_add
以 0
.
开始
static std::atomic<unsigned> write_idx = 0; // shared var
// in each thread:
unsigned my_buf = write_idx.fetch_add(1) & ((1<<size) - 1);
您将获得以 0
而非 1
开头的值。对于许多用例来说,这似乎是一种合理的模式。
是什么设计目的或技术限制使std::fetch_add
的return值变成了更改前的值?
这两种方式都没什么大不了的,你可以模仿另一种方式。例如如果需要,val.add_fetch(1)
可以用 1 + val.add_fetch(1)
实现。然而,GNU C __atomic
builtins provide both.
ISO C/C++ 仅提供 fetch_add
而不是 add_fetch
的可能原因:在某些情况下,它使得在 x86 上实现更便宜; lock xadd [mem], reg
留下 reg = mem 的旧值,mem = sum。提供原语而不是其他原语可以鼓励人们围绕该构建块设计算法,也许可以避免需要额外的 add
指令。
大多数具有 LL/SC 原子的 RISC ISA 有 3 个操作数指令,所以它们可以 add dst, src1, src2
并且如果代码稍后需要它,它们可以将内存中的值原封不动地留在另一个寄存器中。 (LL/SC fetch_add(x)
通常会实现为 load-linked reg1, [mem]
/ add reg2, reg1, x
/ store-conditional reg3, reg2, [mem]
。使用基于 reg3 中 success/fail 结果的重试循环。如果 fetch_add
return 值未使用,add
可以覆盖 reg1
而不是使用新的 reg.
所以在大多数 RISC 上,无论哪种方式都很好,x86 是更相关的 ISA 之一,需要关心效率。
对于某些用例,fetch_add
也是您想要的。例如对于在基于数组的循环缓冲区无锁队列中抓取桶的线程,std::atomic<unsigned> write_idx;
零初始化开始,您希望 .fetch_add
以 0
.
static std::atomic<unsigned> write_idx = 0; // shared var
// in each thread:
unsigned my_buf = write_idx.fetch_add(1) & ((1<<size) - 1);
您将获得以 0
而非 1
开头的值。对于许多用例来说,这似乎是一种合理的模式。