2008-10-28 8 views
14

Редактировать: Код здесь по-прежнему содержит некоторые ошибки, и он может сделать лучше в отделе производительности, но вместо того, чтобы пытаться исправить это, для записи я взял проблему на дискуссионные группы Intel и получил много большой обратной связь, и если все пойдет хорошо полированную версию Atomic поплавка будет включен в ближайшем выпуске будущего от компании Intel Threading Building BlocksЯвляется ли эта реализация на C++ для безопасности Atomic?

Ok здесь жесткий один, я хочу Атомный поплавок, а не для сверхбыстрая производительность графики, но для регулярного использования в качестве элементов данных классов. И я не хочу платить цену за использование замков в этих классах, потому что он не дает никаких дополнительных преимуществ для моих нужд.

Теперь с tbb и другими атомными библиотеками intel, которые я видел, поддерживаются целочисленные типы, но не плавающие точки. Поэтому я продолжил и реализовал один, и он работает ... но я не уверен, работает ли он ДЕЙСТВИТЕЛЬНО, или мне очень повезло, что он работает.

Любое здесь знает, если это не какая-то форма резьбы ересь?

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); 
    } 
    }; 

Спасибо!

Edit: измененная size_t к uint32_t, как предположил Грег Роджерс, что путь его более портативный

Edit: добавил листинг для всей вещи, с некоторыми исправлениями.

Других изменений: Performance мудр с использованием заблокированного поплавка для 5.000.000 + = операций с 100 нитями на моей машине занимает 3.6s, в то время как мой атомный поплавок даже с его глупым делать-то время берет 0.2S, чтобы сделать то же самое Работа. Таким образом, повышение производительности> 30x означает его ценность (и это улов), если это правильно.

Еще больше изменений: Как указал Awgn, мои fetch_and_xxxx части были неправы. Исправлено это и удаленные части API, о которых я не уверен (шаблонные модели памяти). И реализованы другие операции с точки зрения оператора + = во избежание повторения кода

Добавлено: Добавлен оператор * = и operator/=, так как поплавки не будут плавать без них. Благодаря комментарий Peterchen, что это было замечено

Edit: Последняя версия кода следующим образом (я оставлю старую версию для справки, хотя)

#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

Это немного плохо для оператора = для возврата значения, так как на встроенных типах он вычисляет значение lvalue (T &, где T - тип). Для этих типов "(i = j) = k" является необычным, но законным, и присваивает значение k i. – 2008-10-28 14:57:05

+0

Хороший момент, и в моей последней версии кода в ответах.Однако возвращение T, а не lvalue для оператора = является правильным поведением для атомных значений в tbb. – 2008-10-29 03:35:11

+0

эй @ RobertGould. Большое спасибо за реализацию функций и обмен ими. У меня есть два вопроса: (1) все еще актуальны тайминги? Я имею в виду, что на моей платформе я не могу ускорить работу, когда я использую атомную версию, а не `std :: mutex`, (2) есть ли какая-то лицензия для этой части кода? Что делать, если я должен был заимствовать его и заставить его работать с `std :: atomic` в стандартной библиотеке для моего проекта? – 2018-02-20 13:43:14

ответ

5

Я бы серьезно советовал против публичного наследования. Я не знаю, что такое атомная реализация, но я предполагаю, что она перегружена операторами, которые используют ее как интегральный тип, а это значит, что эти рекламные акции будут использоваться вместо вашего поплавка во многих (возможно, в большинстве?) Случаях.

Я не вижу никаких причин, почему это не будет работать, но, как вы мне должны, как доказать, что ...

Одно примечание: ваша operator float() процедура не имеет нагрузки приобретают семантику, и не следует ли это обозначать const volatile (или определенно, по крайней мере, const)?

EDIT: Если вы собираетесь предоставить оператор -(), вы должны предоставить как префиксные, так и постфиксные формы.

3

Похоже, ваша реализация предполагает, что sizeof(size_t) == sizeof(float). Всегда ли это будет верно для ваших целевых платформ?

И я бы не сказал, резьба ереси так много, как литья ереси. :)

+0

Ну не обязательно, но я планирую поставить статическое утверждение, которое сравнивает sizeof (float) == sizeof (size_t) в качестве защитника для компиляции. – 2008-10-28 03:44:45

+0

. Что это заставляет вас использовать только с помощью uint32_t? – 2008-10-28 03:46:13

+0

Хороший момент, мой друг! – 2008-10-28 03:53:22

0

Из моего чтения этого кода я был бы действительно сумасшедшим в таком компиляторе, чтобы выпустить сборку для этого, которая не была атомарной.

0

Попросите компилятор сгенерировать код сборки и взгляните на него. Если операция больше, чем одна инструкция на языке ассемблера, то это не атомная операция и требует, чтобы блокировки работали правильно в многопроцессорных системах.

К сожалению, я не уверен, что верно и обратное - операции с одной инструкцией являются гарантией того, что они будут атомарными. Я не знаю подробностей многопроцессорного программирования вплоть до этого уровня. Я мог бы сделать дело для любого результата. (Если у кого-то еще есть какая-то определенная информация об этом, не стесняйтесь звонить.)

1

Это состояние кода, которое стоит сейчас после разговоров на досках Intel, но до сих пор не было полностью проверено на работу правильно во всех сценариях.

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

Хотя размер uint32_t может быть эквивалентно тому, что из поплавка на данной дуге, по переосмысливая бросок из одного в другой вы неявно предполагая, что атомные приращения, декремент и все другие операции над битами семантически эквивалентны для обоих типов, которые на самом деле не существуют. Я сомневаюсь, что он работает, как ожидалось.

1

Я сильно сомневаюсь, что вы получите правильные значения в fetch_and_add и т. Д., Поскольку сложение по плану отличается от добавления int.

Вот что я получаю от этих арифметике:

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

Так что да, поточно, но не то, что вы ожидаете.

алгоритмы стопорных свободный (оператор + и т.д.) должен работать в отношении атомарности (не проверены для самого алгоритма ..)


Другого решения: Как это все сложение и вычитание, вы можете дать каждому потоку свой собственный экземпляр, а затем добавить результаты из нескольких потоков.

1

Только примечание об этом (я хотел сделать комментарий, но, видимо, новым пользователям не разрешено комментировать): Использование reinterpret_cast по ссылкам приводит к неправильному коду с gcc 4.1 -O3. Это, кажется, исправлено в 4.4, потому что там оно работает. Изменение reinterpret_casts на указатели, хотя и немного уродливое, работает в обоих случаях.

 Смежные вопросы

  • Нет связанных вопросов^_^