有没有更有效的方法可以原子地添加两个浮点数?

Are there any more efficient ways for atomically adding two floats?

本文关键字:添加 浮点数 两个 有效 方法 有没有      更新时间:2023-10-16

我有一堆浮点数,它们由各种线程更新。数组的大小远大于线程数。因此,在特定浮标上同时访问的情况相当罕见。我需要C++03的解决方案。

以下代码以原子方式向其中一个浮点数添加一个值(实时演示)。假设它有效,这可能是最好的解决方案。 我能想到的唯一选择是将数组分成几束,并通过互斥锁保护每一束。但我不认为后者会更有效率。

我的问题如下。有没有原子添加浮点数的替代解决方案?谁能预测哪个是最有效的?是的,我愿意做一些基准测试。也许下面的解决方案可以通过放宽内存顺序限制来改进,即通过其他东西交换__ATOMIC_SEQ_CST。我对此没有经验。

void atomic_add_float( float *x, float add )
{
int *ip_x= reinterpret_cast<int*>( x ); //1
int expected= __atomic_load_n( ip_x, __ATOMIC_SEQ_CST ); //2
int desired;
do  {
float sum= *reinterpret_cast<float*>( &expected ) + add; //3
desired=   *reinterpret_cast<int*>( &sum );
} while( ! __atomic_compare_exchange_n( ip_x, &expected, desired, //4
/* weak = */ true, 
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) );
}

其工作原理如下。在//1x的位模式被解释为int,即我假设floatint具有相同的大小(32位)。在//2,要增加的值以原子方式加载。在//3int的位模式被解释为float并添加总和。(请记住,expected包含位于ip_x == x的值。这不会更改ip_x == x下的值。在//4,求和的结果仅存储在ip_x == x如果没有其他线程更改值,即如果expected == *ip_x(doccu)。如果不是这种情况,则 do-loop 将继续,并且expected包含找到的更新值 adip_x == x

GCC的原子访问函数(__atomic_load_n__atomic_compare_exchange_n)可以很容易地被其他编译器的实现所交换。

有没有其他解决方案可以原子地添加浮点数?谁能预测哪个是最有效的?

当然,至少有几个浮现在脑海中:

  1. 使用同步原语,即自旋锁。会比比较交换慢一点。

  2. 事务扩展(参见维基百科)。会更快,但此解决方案可能会限制可移植性。

总体而言,您的解决方案非常合理:它速度很快,但可以在任何平台上工作。

在我看来,所需的内存顺序是:

  • __ATOMIC_ACQUIRE-- 当我们读取__atomic_load_n()中的值时
  • __ATOMIC_RELEASE-- 当__atomic_compare_exchange_n()就是成功
  • __ATOMIC_ACQUIRE-- 当__atomic_compare_exchange_n()失败时

为了使此函数更有效率,您可能希望将__ATOMIC_ACQUIRE分别用于__atomic_load_n__ATOMIC_RELEASE__ATOMIC_RELAXED用于__atomic_compare_exchange_nsuccess_memorderfailure_memorder

在 x86-64 上,这不会更改生成的程序集,因为它的内存模型相对较强。与内存模型较弱的ARM不同。