这是一个原子浮点的C ++实现是否安全? [英] Is this C++ implementation for an Atomic float safe?

查看:228
本文介绍了这是一个原子浮点的C ++实现是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


编辑:此处的代码仍然有一些错误,它可以在性能部门做得更好,但不是试图解决这个问题,我把问题交给了英特尔讨论组,得到了很多反馈,如果一切顺利,Atomic float的抛光版本将包含在英特尔的线程构建模块的未来版本中。


Ok这里是一个艰难的,我想要一个Atomic浮点,不是为了超快的图形性能,而是常规地用作类的数据成员。我不想支付在这些类上使用锁的代价,因为它不为我的需要提供额外的好处。



现在有了英特尔的tbb和其他原子库,我已经看到,整数类型是支持,但不是浮点。所以我继续和实施一个,它的工作原理...但我不知道如果它真的工作,或者我很幸运,它的工作。



任何人都知道这不是某种形式的线程异端吗?

  typedef unsigned int uint_32; 

struct AtomicFloat
{
private:
tbb :: atomic< uint_32> atomic_value_;

public:
template< memory_semantics M>
float fetch_and_store(float value)
{
const uint_32 value_ = atomic_value_.tbb :: atomic :: fetch_and_store"((uint_32&)value);
return reinterpret_cast< const float&>(value_);
}

float fetch_and_store(float value)
{
const uint_32 value_ = atomic_value_.tbb :: atomic< uint_32> :: fetch_and_store((uint_32& );
return reinterpret_cast< const float&>(value_);
}

template< memory_semantics M>
float compare_and_swap(float value,float comparand)
{
const uint_32 value_ = atomic_value_.tbb :: atomic< uint_32> :: compare_and_swap< M>((uint_32&)value,(uint_32& ;)比较);
return reinterpret_cast< const float&>(value_);
}

float compare_and_swap(float value,float compare)
{
const uint_32 value_ = atomic_value_.tbb :: atomic< uint_32> :: compare_and_swap((uint_32& ;)value,(uint_32&)compare);
return reinterpret_cast< const float&>(value_);
}

操作符float()const volatile //这里为了向后兼容而使用的volatile限定符
{
const uint_32 value_ = atomic_value_;
return reinterpret_cast< const float&>(value_);
}

float operator =(float value)
{
const uint_32 value_ = atomic_value_.tbb :: atomic< uint_32> :: operator =((uint_32& )值);
return reinterpret_cast< const float&>(value_);
}

浮点运算符+ =(浮点值)
{
volatile float old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< float&>(atomic_value_);
new_value_ = old_value_ + value;
} while(compare_and_swap(new_value_,old_value_)!= old_value_);
return(new_value_);
}

浮点运算符* =(浮点值)
{
volatile float old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< float&>(atomic_value_);
new_value_ = old_value_ * value;
} while(compare_and_swap(new_value_,old_value_)!= old_value_);
return(new_value_);
}

浮点运算符/ =(浮点值)
{
volatile float old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< float&>(atomic_value_);
new_value_ = old_value_ / value;
} while(compare_and_swap(new_value_; old_value_)!= old_value_);
return(new_value_);
}

float operator - =(float value)
{
return this-> operator + =( - value);
}

float operator ++()
{
return this-> operator + =(1);
}

float operator - ()
{
return this-> operator + =( - 1);
}

float fetch_and_add(float addend)
{
return this-> operator + =( - addend);
}

float fetch_and_increment()
{
return this-> operator + =(1);
}

float fetch_and_decrement()
{
return this-> operator + =( - 1);
}
};

感谢!



按照Greg Rogers的建议,将size_t更改为uint32_t,这样它更容易为整个事情添加列表



更多编辑:性能明智使用锁定的浮动5.000.000 + =操作100线程在我的机器上3.6 s,而我的原子浮游甚至与它的愚蠢do-while花了0.2s做同样的工作。



更多编辑:作为Awgn指出我的 fetch_and_xxxx 部分都错了。固定和删除的部分API我不知道(模板化内存模型)。

添加了operator * =和operator / =,因为浮点数没有他们就浮动。感谢Peterchen的评论,这是被注意到的



编辑:最新版本的代码如下(我会离开旧版本的参考)

  #include< tbb / atomic.h> 
typedef unsigned int uint_32;
typedef __TBB_LONG_LONG uint_64;

template< typename FLOATING_POINT,typename MEMORY_BLOCK>
struct atomic_float_
{
/ * CRC卡------------------------------- ----------------------
|类:atmomic浮点模板类
|
|责任:处理整数原子内存,因为它是一个浮点,
|但部分绕过FPU,SSE / MMX,所以它是
|比真正的浮动慢,但更快更小
|比锁定的浮子。
| *警告*如果你的浮动使用被阻止
| A-B-A问题这个类不适合你
| *警告*原子规范说我们返回,
|值不是l值。所以(i = j)= k不起作用。
|
|协作者:intel的tbb :: atomic处理内存原子性
------------------------------------ ---------------------------- * /
typedef typename atomic_float_< FLOATING_POINT,MEMORY_BLOCK> self_t;

tbb :: atomic< MEMORY_BLOCK> atomic_value_;

template< memory_semantics M>
FLOATING_POINT fetch_and_store(FLOATING_POINT value)
{
const MEMORY_BLOCK value_ =
atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: fetch_and_store&
//原子规范要求返回旧值,而不是新值
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

FLOATING_POINT fetch_and_store(FLOATING_POINT value)
{
const MEMORY_BLOCK value_ =
atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: fetch_and_store MEMORY_BLOCK&)value);
//原子规范要求返回旧值,而不是新值
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

template< memory_semantics M>
FLOATING_POINT compare_and_swap(FLOATING_POINT value,FLOATING_POINT comparand)
{
const MEMORY_BLOCK value_ =
atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: compare_and_swap< M(MEMORY_BLOCK&值,(MEMORY_BLOCK&)比较);
//原子规范要求返回旧值,而不是新值
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

FLOATING_POINT compare_and_swap(FLOATING_POINT值,FLOATING_POINT比较)
{
const MEMORY_BLOCK value_ =
atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: compare_and_swap((MEMORY_BLOCK&)value,(MEMORY_BLOCK&)compare);
//原子规范要求返回旧值,而不是新值
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

运算符FLOATING_POINT()const volatile //这里为了向后兼容性的volatile限定符
{
const MEMORY_BLOCK value_ = atomic_value_;
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

//注意:原子规范说我们返回一个基值的副本不是一个l值
FLOATING_POINT operator =(FLOATING_POINT rhs)
{
const MEMORY_BLOCK value_ = atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: operator =((MEMORY_BLOCK&)rhs);
return reinterpret_cast< const FLOATING_POINT&>(value_);
}

//注意:原子规范说我们在原子操作中返回一个l值
self_t& operator =(self_t& rhs)
{
const MEMORY_BLOCK value_ = atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: operator =((MEMORY_BLOCK&)rhs);
return * this;
}

FLOATING_POINT& _internal_reference()const
{
return reinterpret_cast< FLOATING_POINT&>(atomic_value_.tbb :: atomic< MEMORY_BLOCK> :: _ internal_reference());
}

FLOATING_POINT operator + =(FLOATING_POINT value)
{
FLOATING_POINT old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< FLOATING_POINT&>(atomic_value_);
new_value_ = old_value_ + value;
//浮点二进制表示不是一个问题,因为
//我们使用我们的self的比较和交换,因此比较浮点和浮点
} while(self_t :: compare_and_swap(new_value_,old_value_ )!= old_value_);
return(new_value_); //返回结果值
}

FLOATING_POINT运算符* =(FLOATING_POINT值)
{
FLOATING_POINT old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< FLOATING_POINT&>(atomic_value_);
new_value_ = old_value_ * value;
//浮点二进制表示不是一个问题因为
//我们使用我们的self的比较和交换,因此比较浮点和浮点
} while(self_t :: compare_and_swap(new_value_,old_value_ )!= old_value_);
return(new_value_); //返回结果值
}

FLOATING_POINT operator / =(FLOATING_POINT value)
{
FLOATING_POINT old_value_,new_value_;
do
{
old_value_ = reinterpret_cast< FLOATING_POINT&>(atomic_value_);
new_value_ = old_value_ / value;
//浮点二进制表示不是一个问题,因为
//我们使用我们的self的比较和交换,因此比较浮点和浮点
} while(self_t :: compare_and_swap(new_value_,old_value_ )!= old_value_);
return(new_value_); //返回结果值
}

FLOATING_POINT operator - =(FLOATING_POINT value)
{
return this-> operator + =( - value); //返回结果值
}

//前缀运算符
FLOATING_POINT operator ++()
{
return this-> operator + =(1) //返回结果值
}

//前缀运算符
FLOATING_POINT运算符 - ()
{
return this-> operator + = 1); //返回结果值
}

//后缀运算符
FLOATING_POINT运算符++(int)
{
const FLOATING_POINT temp = this;
this-> operator + =(1);
return temp //返回结果值
}

// Postfix操作符
FLOATING_POINT操作符 - (int)
{
const FLOATING_POINT temp = this;
this-> operator + =(1);
return temp //返回结果值
}

FLOATING_POINT fetch_and_add(FLOATING_POINT addend)
{
const FLOATING_POINT old_value_ = atomic_value_;
this-> operator + =(addend);
//原子规范要求返回旧值,而不是新的值,如运算符x =
return old_value_;
}

FLOATING_POINT fetch_and_increment()
{
const FLOATING_POINT old_value_ = atomic_value_;
this-> operator + =(+ 1);
//原子规范要求返回旧值,而不是新的值,如运算符x =
return old_value_;
}

FLOATING_POINT fetch_and_decrement()
{
const FLOATING_POINT old_value_ = atomic_value_;
this-> operator + =( - 1);
//原子规范要求返回旧值,而不是新的值,如运算符x =
return old_value_;
}
};

typedef atomic_float_< float,uint_32>原子浮力
typedef atomic_float_< double,uint_64>原子双


解决方案

我会认真反对公共继承。我不知道原子实现是什么样的,但im假设它有重载的操作符,使用它作为整数类型,这意味着那些促销将被使用,而不是你的浮动在许多(也许最多的)情况。 p>

我没有看到任何原因,为什么这不行,但是像你一样,我必须证明...



一个注意:你的操作符float()例程没有加载获取语义,并且不应该标记const volatile(或者至少const )?



编辑:如果你要提供operator - (),你应该提供前缀/后缀形式。


Edit: The code here still has some bugs in it, and it could do better in the performance department, but instead of trying to fix this, for the record I took the problem over to the Intel discussion groups and got lots of great feedback, and if all goes well a polished version of Atomic float will be included in a near future release of Intel's Threading Building Blocks

Ok here's a tough one, I want an Atomic float, not for super-fast graphics performance, but to use routinely as data-members of classes. And I don't want to pay the price of using locks on these classes, because it provides no additional benefits for my needs.

Now with intel's tbb and other atomic libraries I've seen, integer types are supported, but not floating points. So I went on and implemented one, and it works... but I'm not sure if it REALLY works, or I'm just very lucky that it works.

Anyone here knows if this is not some form of threading heresy?

typedef unsigned int uint_32;

  struct AtomicFloat
  {
    private:
    tbb::atomic<uint_32> atomic_value_;

    public:
    template<memory_semantics M>
    float fetch_and_store( float value ) 
    {
    	const uint_32 value_ = atomic_value_.tbb::atomic<uint_32>::fetch_and_store<M>((uint_32&)value);
    	return reinterpret_cast<const float&>(value_);
    }

    float fetch_and_store( float value ) 
    {
    	const uint_32 value_ = atomic_value_.tbb::atomic<uint_32>::fetch_and_store((uint_32&)value);
    	return reinterpret_cast<const float&>(value_);
    }

    template<memory_semantics M>
    float compare_and_swap( float value, float comparand ) 
    {
    	const uint_32 value_ = atomic_value_.tbb::atomic<uint_32>::compare_and_swap<M>((uint_32&)value,(uint_32&)compare);
    	return reinterpret_cast<const float&>(value_);
    }

    float compare_and_swap(float value, float compare)
    {
    	const uint_32 value_ = atomic_value_.tbb::atomic<uint_32>::compare_and_swap((uint_32&)value,(uint_32&)compare);
    	return reinterpret_cast<const float&>(value_);
    }

    operator float() const volatile // volatile qualifier here for backwards compatibility 
    {
    	const uint_32 value_ = atomic_value_;
    	return reinterpret_cast<const float&>(value_);
    }

    float operator=(float value)
    {
    	const uint_32 value_ = atomic_value_.tbb::atomic<uint_32>::operator =((uint_32&)value);
    	return reinterpret_cast<const float&>(value_);
    }

    float operator+=(float value)
    {
    	volatile float old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<float&>(atomic_value_);
    		new_value_ = old_value_ + value;
    	} while(compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_);
    }

    float operator*=(float value)
    {
    	volatile float old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<float&>(atomic_value_);
    		new_value_ = old_value_ * value;
    	} while(compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_);
    }

    float operator/=(float value)
    {
    	volatile float old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<float&>(atomic_value_);
    		new_value_ = old_value_ / value;
    	} while(compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_);
    }

    float operator-=(float value)
    {
    	return this->operator+=(-value);
    }

    float operator++() 
    {
    	return this->operator+=(1);
    }

    float operator--() 
    {
    	return this->operator+=(-1);
    }

    float fetch_and_add( float addend ) 
    {
    	return this->operator+=(-addend);
    }

    float fetch_and_increment() 
    {
    	return this->operator+=(1);
    }

    float fetch_and_decrement() 
    {
    	return this->operator+=(-1);
    }
   };

Thanks!

Edit: changed size_t to uint32_t as Greg Rogers suggested, that way its more portable

Edit: added listing for the entire thing, with some fixes.

More Edits: Performance wise using a locked float for 5.000.000 += operations with 100 threads on my machine takes 3.6s, while my atomic float even with its silly do-while takes 0.2s to do the same work. So the >30x performance boost means its worth it, (and this is the catch) if its correct.

Even More Edits: As Awgn pointed out my fetch_and_xxxx parts were all wrong. Fixed that and removed parts of the API I'm not sure about (templated memory models). And implemented other operations in terms of operator += to avoid code repetition

Added: Added operator *= and operator /=, since floats wouldn't be floats without them. Thanks to Peterchen's comment that this was noticed

Edit: Latest version of the code follows (I'll leave the old version for reference though)

  #include <tbb/atomic.h>
  typedef unsigned int  	uint_32;
  typedef __TBB_LONG_LONG   	uint_64;

  template<typename FLOATING_POINT,typename MEMORY_BLOCK>
  struct atomic_float_
  {
    /*	CRC Card -----------------------------------------------------
    |	Class:			atmomic float template class
    |
    |	Responsability:	handle integral atomic memory as it were a float,
    |					but partially bypassing FPU, SSE/MMX, so it is
    |					slower than a true float, but faster and smaller
    |					than a locked float.
    |						*Warning* If your float usage is thwarted by
    |					the A-B-A problem this class isn't for you
    |						*Warning* Atomic specification says we return,
    |					values not l-values. So  (i = j) = k doesn't work.
    |
    |	Collaborators:	intel's tbb::atomic handles memory atomicity
    ----------------------------------------------------------------*/
    typedef typename atomic_float_<FLOATING_POINT,MEMORY_BLOCK>	self_t;

    tbb::atomic<MEMORY_BLOCK> atomic_value_;

    template<memory_semantics M>
    FLOATING_POINT fetch_and_store( FLOATING_POINT value ) 
    {
    	const MEMORY_BLOCK value_ = 
    		atomic_value_.tbb::atomic<MEMORY_BLOCK>::fetch_and_store<M>((MEMORY_BLOCK&)value);
    	//atomic specification requires returning old value, not new one
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    FLOATING_POINT fetch_and_store( FLOATING_POINT value ) 
    {
    	const MEMORY_BLOCK value_ = 
    		atomic_value_.tbb::atomic<MEMORY_BLOCK>::fetch_and_store((MEMORY_BLOCK&)value);
    	//atomic specification requires returning old value, not new one
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    template<memory_semantics M>
    FLOATING_POINT compare_and_swap( FLOATING_POINT value, FLOATING_POINT comparand ) 
    {
    	const MEMORY_BLOCK value_ = 
    		atomic_value_.tbb::atomic<MEMORY_BLOCK>::compare_and_swap<M>((MEMORY_BLOCK&)value,(MEMORY_BLOCK&)compare);
    	//atomic specification requires returning old value, not new one
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    FLOATING_POINT compare_and_swap(FLOATING_POINT value, FLOATING_POINT compare)
    {
    	const MEMORY_BLOCK value_ = 
    		atomic_value_.tbb::atomic<MEMORY_BLOCK>::compare_and_swap((MEMORY_BLOCK&)value,(MEMORY_BLOCK&)compare);
    	//atomic specification requires returning old value, not new one
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    operator FLOATING_POINT() const volatile // volatile qualifier here for backwards compatibility 
    {
    	const MEMORY_BLOCK value_ = atomic_value_;
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    //Note: atomic specification says we return the a copy of the base value not an l-value
    FLOATING_POINT operator=(FLOATING_POINT rhs) 
    {
    	const MEMORY_BLOCK value_ = atomic_value_.tbb::atomic<MEMORY_BLOCK>::operator =((MEMORY_BLOCK&)rhs);
    	return reinterpret_cast<const FLOATING_POINT&>(value_);
    }

    //Note: atomic specification says we return an l-value when operating among atomics
    self_t& operator=(self_t& rhs) 
    {
    	const MEMORY_BLOCK value_ = atomic_value_.tbb::atomic<MEMORY_BLOCK>::operator =((MEMORY_BLOCK&)rhs);
    	return *this;
    }

    FLOATING_POINT& _internal_reference() const
    {
    	return reinterpret_cast<FLOATING_POINT&>(atomic_value_.tbb::atomic<MEMORY_BLOCK>::_internal_reference());
    }

    FLOATING_POINT operator+=(FLOATING_POINT value)
    {
    	FLOATING_POINT old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<FLOATING_POINT&>(atomic_value_);
    		new_value_ = old_value_ + value;
    	//floating point binary representation is not an issue because
    	//we are using our self's compare and swap, thus comparing floats and floats
    	} while(self_t::compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_); //return resulting value
    }

    FLOATING_POINT operator*=(FLOATING_POINT value)
    {
    	FLOATING_POINT old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<FLOATING_POINT&>(atomic_value_);
    		new_value_ = old_value_ * value;
    	//floating point binary representation is not an issue becaus
    	//we are using our self's compare and swap, thus comparing floats and floats
    	} while(self_t::compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_); //return resulting value
    }

    FLOATING_POINT operator/=(FLOATING_POINT value)
    {
    	FLOATING_POINT old_value_, new_value_;
    	do
    	{
    		old_value_ = reinterpret_cast<FLOATING_POINT&>(atomic_value_);
    		new_value_ = old_value_ / value;
    	//floating point binary representation is not an issue because
    	//we are using our self's compare and swap, thus comparing floats and floats
    	} while(self_t::compare_and_swap(new_value_,old_value_) != old_value_);
    	return (new_value_); //return resulting value
    }

    FLOATING_POINT operator-=(FLOATING_POINT value)
    {
    	return this->operator+=(-value); //return resulting value
    }

    //Prefix operator
    FLOATING_POINT operator++()
    {
    	return this->operator+=(1); //return resulting value
    }

    //Prefix operator
    FLOATING_POINT operator--() 
    {
    	return this->operator+=(-1); //return resulting value
    }

    //Postfix operator
    FLOATING_POINT operator++(int)
    {
    	const FLOATING_POINT temp = this;
    	this->operator+=(1);
    	return temp//return resulting value
    }

    //Postfix operator
    FLOATING_POINT operator--(int) 
    {
    	const FLOATING_POINT temp = this;
    	this->operator+=(1);
    	return temp//return resulting value
    }

    FLOATING_POINT fetch_and_add( FLOATING_POINT addend ) 
    {
    	const FLOATING_POINT old_value_ = atomic_value_;
    	this->operator+=(addend);
    	//atomic specification requires returning old value, not new one as in operator x=
    	return old_value_; 
    }

    FLOATING_POINT fetch_and_increment() 
    {
    	const FLOATING_POINT old_value_ = atomic_value_;
    	this->operator+=(+1);
    	//atomic specification requires returning old value, not new one as in operator x=
    	return old_value_; 
    }

    FLOATING_POINT fetch_and_decrement() 
    {
    	const FLOATING_POINT old_value_ = atomic_value_;
    	this->operator+=(-1);
    	//atomic specification requires returning old value, not new one as in operator x=
    	return old_value_; 
    }
  };

  typedef atomic_float_<float,uint_32> AtomicFloat;
  typedef atomic_float_<double,uint_64> AtomicDouble;

解决方案

I would seriously advise against public inheritance. I don't know what the atomic implementation is like, but im assuming it has overloaded operators that use it as the integral type, which means that those promotions will be used instead of your float in many (maybe most?) cases.

I don't see any reason why that wouldn't work, but like you I have to way to prove that...

One note: your operator float() routine does not have load-acquire semantics, and shouldn't it be marked const volatile (or definitely at least const)?

EDIT: If you are going to provide operator--() you should provide both prefix/postfix forms.

这篇关于这是一个原子浮点的C ++实现是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆