문제

편집하다: 여기에 코드에는 여전히 몇 가지 버그가 있으며 성능 부서에서는 더 잘할 수 있지만,이 문제를 해결하려고 시도하는 대신, 레코드를 위해 인텔 토론 그룹에 문제를 가져 와서 많은 피드백을 얻었습니다. Atomic Float의 세련된 버전은 인텔의 스레딩 빌딩 블록의 가까운 향후 출시에 포함됩니다.

좋아요 여기에 힘든 것이 있습니다. 저는 초고속 그래픽 성능을위한 것이 아니라 클래스의 데이터 멤버로 일상적으로 사용하는 원자 플로트를 원합니다. 그리고 나는이 클래스에서 자물쇠를 사용하는 가격을 지불하고 싶지 않습니다. 왜냐하면 그것은 내 요구에 대한 추가 혜택을 제공하지 않기 때문입니다.

이제 인텔의 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<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);
    }
   };

감사!

편집하다: Greg Rogers가 제안한 것처럼 size_t를 UINT32_T로 변경했습니다.

편집하다: 몇 가지 수정으로 전체에 대한 목록이 추가되었습니다.

더 많은 편집 : 성능은 5.000.000 += 내 컴퓨터에 100 개의 스레드가있는 작업을 위해 잠긴 플로트를 사용하는 것이 현명한 3.6 초가 걸리며, 바보 같은 작업을 수행하더라도 원자 플로트는 동일한 작업을 수행하는 데 0.2 초가 걸립니다. 따라서> 30 배의 성능 부스트는 그만한 가치를 의미합니다.

더 많은 편집 : Awgn이 내 말을 지적했듯이 fetch_and_xxxx 부품이 모두 잘못되었습니다. 그것을 고정시키고 API의 일부를 제거했습니다 (템플릿 메모리 모델). 연산자 측면에서 다른 작업을 구현 += 코드 반복을 피하기 위해

추가 : 부유물은 플로트가 없어서 부유물이 없기 때문에 연산자 *= 및 연산자 /=가 추가되었습니다. 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 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;
도움이 되었습니까?

해결책

나는 대중의 상속에 대해 진지하게 조언 할 것이다. 원자 구현이 어떤지 모르겠지만,이를 통합 유형으로 사용하는 오버로드 된 연산자가 있다고 가정합니다. 즉, 많은 (아마도 대부분) 케이스에서 플로트 대신에 그 프로모션이 사용될 것입니다.

그 이유가 작동하지 않는 이유는 보이지 않지만, 당신처럼 나는 그것을 증명해야합니다 ...

하나의 참고 사항 : 귀하의 operator float() 루틴은로드 아카이어 의미를 가지고 있지 않으며, 휘발성 (또는 적어도 const)으로 표시되어서는 안됩니까?

편집 : 연산자를 제공하려는 경우-() 접두사/postfix 양식을 모두 제공해야합니다.

다른 팁

구현이 그것을 가정하는 것처럼 보입니다 sizeof(size_t) == sizeof(float). 대상 플랫폼에 항상 사실이 될까요?

그리고 나는 말하지 않을 것입니다 스레딩 이단만큼 주조 이교. :)

a의 크기에도 불구하고 UINT32_T a의 것과 동일 할 수 있습니다 뜨다 주어진 아치에서, 주어진 아치에서, 하나에서 다른쪽으로 캐스트를 재 해석함으로써, 당신은 원자 증가, 감소 및 비트의 다른 모든 작업이 현실에 있지 않은 두 유형 모두 의미 적으로 동일하다고 가정합니다. 나는 그것이 예상대로 작동하는 것을 의심합니다.

플로트 첨가가 int 첨가와 다르기 때문에 Fetch_and_add 등에서 올바른 값을 얻는 것이 의심 스럽다.

이 산술에서 얻은 내용은 다음과 같습니다.

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

그렇습니다. ThreadSafe이지만 당신이 기대하는 것은 아닙니다.

잠금 알고리즘 (연산자 + 등)은 원자력과 관련하여 작동해야합니다 (알고리즘 자체를 확인하지 않았습니다.)


기타 해결책 : 모든 추가 및 뺄셈이므로 모든 스레드에 자체 인스턴스를 제공 한 다음 여러 스레드의 결과를 추가 할 수 있습니다.

이것은 인텔 보드에서 대화를 나눈 후에 서있는 코드의 상태이지만 모든 시나리오에서 올바르게 작동하도록 철저히 검증되지는 않았습니다.

  #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;

이것에 대한 메모 만 (댓글을 작성하고 싶었지만 분명히 새로운 사용자는 댓글을 달 수 없습니다) : 참조에서 RENETERPRET_CAST를 사용하면 GCC 4.1 -o3로 잘못된 코드를 생성합니다. 이것은 작동하기 때문에 4.4에 고정 된 것 같습니다. RenterPret_casts를 포인터로 변경하면 약간 추악하지만 두 경우 모두 작동합니다.

그 코드를 읽음으로써, 나는 원자가 아닌 이에 대한 조립품을 내놓을 수있는 컴파일러에 정말로 화를 낼 것입니다.

컴파일러가 어셈블리 코드를 생성하고이를 살펴 보도록하십시오. 작업이 단일 어셈블리 언어 명령 이상이라면 ~ 아니다 원자 작동이며 멀티 프로세서 시스템에서 제대로 작동하려면 잠금 장치가 필요합니다.

불행히도, 나는 그 반대가 사실인지 확신하지 못합니다. ~이다 원자임을 보장합니다. 멀티 프로세서 프로그래밍의 세부 사항이 해당 레벨까지 모릅니다. 두 결과 모두 사례를 만들 수 있습니다. (다른 사람이 그것에 대한 결정적인 정보가 있다면, 자유롭게 차임하십시오.)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top