Атомарные операции над поплавками

Большинство языков предоставляют функции для атомарных int операций (добавление, сравнение и замена и т. д.).

Почему не для типов с плавающей запятой?


person Erik Aigner    schedule 07.01.2014    source источник


Ответы (4)


Давайте подумаем об атомарности с плавающей запятой с точки зрения проектирования ОС/железа...

Атомары существуют, потому что они нужны для синхронизации. Что включает в себя большая часть синхронизации? Дескрипторы, флаги, мьютексы, спин-блокировки — вещи, фактическое значение которых бессмысленно, пока оно согласовано для каждого пользователя и различается между пользователями. Даже для чего-то вроде семафора, где значение имеет большее значение, речь идет о счете, а не о измерении, поэтому 32 бита стоят 32 бита, что бы мы ни представляли.

Во-вторых, технические проблемы. Почти все, что мы можем запрограммировать, выполняет целочисленные операции. Не так с плавающей запятой - когда операции FP эмулируются библиотекой C, эти атомы будут между трудными и невозможными для реализации. Даже на аппаратном уровне операции FP обычно будут медленнее, чем целочисленные, а кому нужны медленные блокировки? Конструкция самого FPU может даже затруднить реализацию атомарных операций, например. если он висит на интерфейсе сопроцессора без прямого доступа к шине памяти.

Секунда с половиной, если мы хотим float, наверняка мы также хотим double? Но double часто имеет проблему, заключающуюся в том, что он больше, чем машинное слово, что исключает атомарность даже загрузки и сохранения на многих архитектурах.

В-третьих, когда дело доходит до таких вещей, как атомарность, архитекторы ЦП, как правило, реализуют то, что требуют системные дизайнеры и разработчики ОС, а разработчики ОС вообще не очень любят числа с плавающей запятой — дурацкие дополнительные регистры для сохранения, замедляющие переключение контекста... Дополнительные инструкции/функции в аппаратном обеспечении стоят мощности и сложности, и если клиенты не хотят этого...

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

person Notlikethat    schedule 08.01.2014

Чтобы улучшить предыдущие ответы в контексте Go, мы можем использовать https://golang.org/pkg/math/#Float64bits и https://golang.org/pkg/math/#Float64frombits для преобразования float64 в uint64 и обратно без прямого использования пакета unsafe.

Получив uint64, мы можем использовать все доступные атомарные примитивы.

type AtomicFloat64 uint64

func (f *AtomicFloat64) Value() float64 {
    return math.Float64frombits(atomic.LoadUint64((*uint64)(f)))
}

func (f *AtomicFloat64) Add(n float64) float64 {
    for {
        a := atomic.LoadUint64((*uint64)(f))
        b := math.Float64bits(math.Float64frombits(a) + n)
        if atomic.CompareAndSwapUint64((*uint64)(f), a, b) {
            return
        }
    }
}
person Tomás Senart    schedule 04.10.2015
comment
Этот ответ неверен. Вы можете попробовать ``` var x AtomicFloat64 x.Add(1) x.Add(1) x.Add(1) x.Value() -> -0.25, конечно, не 3 ``` Если вы хотите лучший способ, проверьте это код: github.com/prometheus/client_golang/blob/master/ прометей/ - person zviadm; 08.01.2016
comment
Вы совершенно правы! В этом коде есть ошибка. Спасибо, что заметили, я обновлю ответ. - person Tomás Senart; 16.01.2016
comment
Нет необходимости делать a := atomic.LoadUint64, потому что это может измениться до следующего оператора if. См. golang.org/src/sync/atomic/64bit_arm. .go?s=508:615#L27 - person Sergey Kamardin; 12.07.2017

Ах, исходный вопрос был помечен как Go и немного отличался, так что вот на что я все равно собирался ответить, извините, если это не полный ответ на отредактированный вопрос :)

С помощью Go вы можете атомарно поменять местами любое значение указателя, сделав небольшое путешествие на небезопасную темную сторону:

http://play.golang.org/p/aFbzUMhfEB

Выдержка:

var uptr unsafe.Pointer
var f1, f2 float64

f1 = 12.34
f2 = 56.78
// Original values
fmt.Println(f1, f2)
uptr = unsafe.Pointer(&f1)
atomic.SwapPointer(&uptr, unsafe.Pointer(&f2))
f1 = *(*float64)(unsafe.Pointer(uptr))
// f1 now holds the value of f2, f2 is untouched
fmt.Println(f1, f2)

Вызов подкачки (и другие атомарные операции, такие как CAS) сопоставляется с архитектурой ЦП. инструкция, которая гарантирует эту атомарность (подробнее см. https://stackoverflow.com/a/1762179/1094941). Что касается того, почему нет поддержки сборки для поплавков, я не знаю.

person mna    schedule 07.01.2014
comment
OpenCL предлагает атомарные операции с плавающей запятой одинарной точности. Он поддерживает немного меньшее подмножество C. khronos.org Он должен быть переносимым, насколько мне известно. - person jim mcnamara; 08.01.2014

CAS (compare-and-swap) — самая важная атомарная операция, поскольку другие операции (add, and, or и т. д.) можно моделировать в ее терминах.

Я думаю, что важная причина, по которой числа с плавающей запятой не имеют функциональности CAS, заключается в том, что равенство не работает с числами с плавающей запятой IEEE754 так же, как с целочисленными типами. Например, вы не будете знать, был ли своп успешным с CAS, если старое ожидаемое или фактическое значение оказалось NaN. Помните, что сравнение NaN с любым другим значением, включая NaN, всегда возвращает false.

Что касается атомарных побитовых и арифметических операций, они просто намного менее полезны для операций с плавающей запятой, чем для целых чисел.

person lehins    schedule 02.05.2020