Is this C++ implementation for an Atomic float safe?

前端 未结 8 1525
有刺的猬
有刺的猬 2020-12-30 05:06

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 t

相关标签:
8条回答
  • 2020-12-30 05:43

    From my reading of that code, I would be really mad at such a compiler as to put out assembly for this that wasn't atomic.

    0 讨论(0)
  • 2020-12-30 05:45

    This is the state of the code as it stands now after talks on the intel boards, but still hasn't been thoroughly verified to work correctly in all scenarios.

      #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;
    
    0 讨论(0)
  • 2020-12-30 05:45

    Have your compiler generate assembly code and take a look at it. If the operation is more than a single assembly-language instruction, then it's not an atomic operation, and requires locks to operate properly in multiprocessor systems.

    Unfortunately, I'm not certain that the opposite is also true -- that single-instruction operations are guaranteed to be atomic. I don't know the details of multiprocessor programming down to that level. I could make a case for either result. (If anyone else has some definitive information on that, feel free to chime in.)

    0 讨论(0)
  • 2020-12-30 05:48

    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.

    0 讨论(0)
  • 2020-12-30 05:52

    I strongly doubt that you get the correct values in fetch_and_add etc, as float addition is different from int addition.

    Here's what I get from these arithmetics:

    1   + 1    =  1.70141e+038  
    100 + 1    = -1.46937e-037  
    100 + 0.01 =  1.56743e+038  
    23  + 42   = -1.31655e-036  
    

    So yeah, threadsafe but not what you expect.

    the lock-free algorithms (operator + etc.) should work regarding atomicity (haven't checked for the algorithm itself..)


    Other solution: As it is all additions and subtractions, you might be able to give every thread its own instance, then add the results from multiple threads.

    0 讨论(0)
  • 2020-12-30 05:55

    It looks like your implementation assumes that sizeof(size_t) == sizeof(float). Will that always be true for your target platforms?

    And I wouldn't say threading heresy so much as casting heresy. :)

    0 讨论(0)
提交回复
热议问题