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

时间:2018-02-12 12:29:35

标签: c++ multithreading atomic c++03

我有一个浮动包,可以通过各种线程进行更新。数组的大小远大于线程数。因此,对特定浮标的同时访问是相当罕见的。我需要一个C ++ 03的解决方案。

以下代码以原子方式向其中一个浮点数(live demo)添加值。假设它有效,它可能是最好的解决方案。 我能想到的唯一选择是将数组分成多个串并用互斥锁保护每个串。但我不希望后者更有效率。

我的问题如下。是否存在原子添加浮子的替代解决方案?任何人都可以预见哪种效率最高?是的,我愿意做一些基准测试。也许下面的解决方案可以通过放松memorder约束来改进,即通过其他东西交换__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 == xdocu),则总和的结果仅存储在expected == *ip_x。如果不是这种情况,则执行循环继续,expected包含找到的广告ip_x == x的更新值。

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

2 个答案:

答案 0 :(得分:1)

为了提高此功能的效率,您可以使用__ATOMIC_ACQUIRE __atomic_load_n __ATOMIC_RELEASE__ATOMIC_RELAXED __atomic_compare_exchange_n success_memorder和{分别为{1}}。

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

答案 1 :(得分:1)

  

是否存在原子添加浮子的替代解决方案?谁能预测哪种效率最高?

当然,至少有几个想到的东西:

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

  2. 交易扩展(see Wikipedia)。会更快,但这个解决方案可能会限制可移植性。

  3. 总的来说,您的解决方案非常合理:它很快,但可以在任何平台上运行。

    在我看来,所需的记忆顺序是:

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