原文:Is atomic decrementing more expensive than incrementing? 翻译:原子性地递减比递增更昂贵吗?

18
在他的Herb Sutter博客中写道:“因为在优化的shared_ptr实现中,智能指针引用计数的递增通常可以被优化为与普通递增相同 - 只是普通的递增指令,而没有屏障,在生成的代码中。”
然而,“递减必须是原子递减或等效的,这会生成更昂贵的特殊处理器内存指令,它们本身就会诱发内存栅栏限制,进一步优化周围代码。”
这段文字是关于shared_ptr的实现,我不确定他的评论是否仅适用于此,还是普遍适用。从他的措辞中,我得出结论它是普遍适用的。
但是当我考虑它时,我只能想到“更昂贵的递减”,当if(counter==0)紧随其后时 - 这可能是shared_ptr的情况。
因此,我想知道原子操作++counter是否(通常)始终比--counter快,还是仅仅因为它与shared_ptr一起使用if(--counter==0)...

1
从他的表述中,我推断通常情况是这样。 - 不过,我推断出相反的情况。 - Christian Rau
你可能还想看一下WriteRead屏障,这是--counter == 0所需的,通常是最昂贵的屏障类型,因为它是唯一要求在写入数据后立即重新读取以确保核之间同步的屏障类型。对于counter++,您只需要编写而无需立即要求获取该值或其他数据的同步状态。 - Red XIII
3个回答

16
他在某个地方更详细地讨论了这一点,我想他在他的atomic<> weapons演示中。基本上,这全部关乎于共享指针使用情况下需要内存屏障的位置,而不是原子增量与减量的内在属性。
原因在于当使用引用计数智能指针时,您不会真正关心增量的确切顺序,只要不漏掉任何一个就可以了,但是减量则必须放置内存屏障,以便在触发删除操作的最后一个减量时,拥有智能指针所拥有的对象的先前内存访问不会被重排到释放内存之后。

Herb的演讲超级棒!谢谢!+1 - towi
“只要你不错过任何一个”是重要的部分。此外,还有针对减量的happens-before保证(但这由原子减量处理)。他明确表示“正常增量”,在并发场景下会错过一些增量。也许他本意是说其他的,但无论如何这就是他说的话。 - Damon

11

我认为这指的是增量可以“隐藏”,而“递减和检查”必须作为一个操作完成。

就我所知,没有任何架构使--counter(或counter--,假设我们谈论的是简单数据类型如int,char等)比++countercounter++慢。


“hidden” 是什么意思? - curiousguy
这与他所说的完全相反。他明确表示“普通增量”。这不是关于之后不检查值或任何类似的事情。它是关于使用普通增量(读取缓存行,修改,写入缓存行),在并发存在的情况下将会丢失引用。因此,我强烈倾向于说他所说的是错误的。Sutter 也可能会说错话,为什么不呢。 - Damon

10
Sutter所谈论的问题是引用计数增量不需要任何后续操作来保证正确性。你将非零引用计数转换为另一个非零计数,因此不需要进一步的操作。然而,减量需要进行后续操作以确保正确性。该减量将非零引用计数转换为非零或零的引用计数,如果减量的引用计数变为零,则需要执行一项操作——具体地说,释放被引用的对象。这种减量和操作的动态要求更高的一致性,无论是在围栏级别上(这样释放操作不会与CPU的内存/缓存管理逻辑重排到另一个核心上的某些其他读/写操作之间),还是在编译器级别上(这样编译器不会在减量周围重新排序操作,可能会导致读/写操作在潜在的释放操作周围重新排序)。
因此,对于Sutter描述的情况,增量和减量的成本差异不在于基本操作本身,而在于实际使用减量时强制执行的一致性约束(特别是对于减量本身的操作)。这些约束不适用于增量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接