Why isn’t `Double` usable with swift-atomics?

a Double is eight bytes long, and ought to be usable with swift-atomics. yet it doesn’t conform to AtomicValue for some reason.

why?

Looks like it is planned:

1 Like

Here is the relevant ticket, which alludes to but does not fully cover some of the issues with atomic doubles.

More broadly, atomic floating point is kind of a pain in the ass. Few CPUs define a full set of atomic floating point operations. This tends to force users to define their atomic floating points on top of atomic integers, storing the raw bits and then interpreting the raw bits as a floating point number to perform the operation. This has the following implications:

  1. Many atomic operations now contain a hidden CAS-loop. For any operation (e.g. fmul) that doesn't have an atomic equivalent, it becomes necessary to implement it using a compare-and-swap loop. The performance of this degrades fairly substantially under contention.
  2. Checking equality is painful. Floating point numbers don't define equality by way of bitwise identity, but in this instance we wouldn't notice that in the trivial case.

While I can personally believe that there are some uses for atomic doubles, I've tended to be suspicious of them in most code. They tend to smell of premature optimization, where value semantics or locks end up behaving better. Their fast-path is not substantially better than an uncontended lock on most platforms (which is itself a single RMW) so you have to really consider what you're trying to achieve. Nonetheless, it makes sense for swift-atomics to offer what it can.

(Sidebar: arm64 prior to armv8.1 made all atomic operations an atomic RMW so there's no reason to be excessively suspicious of this pattern, but armv8.1 still felt the need to add single-operation atomics.)

3 Likes

neither of these limitations are relevant to me, because i am not performing any increments or comparisons with the Double, i am merely trying to use the atomic to publish a floating point value from an actor.

the procedure looks a bit like this:

  1. actor computes some floating point statistic (let’s call it a running average) in an isolated context.
  2. at the end of every statistic update, the actor publishes that statistic to an atomic that code outside the actor can read without contending for the actor. the actor never reads or increments the atomic, it only ever sets it.

i do not understand why everyone’s default response to this kind of question is “just use locks instead”. in my view, actors have a limitation where they cannot expose any kind of mutable property without isolation.

public
actor NetworkStatistics
{
    public private(set)
    var ninetiethPercentileLatency:Double
}

updating the latency requires sequencing, because we don’t want to accidentally overwrite a newer statistic with an older one. but reading it really should not require any synchronization, because it is only a derived value.

A bit inconvenient, but will this simple wrapper work for you?

extension UnsafeAtomic where Value == UInt64 {
    public var double: Double {
        get { Double(bitPattern: load(ordering: .relaxed)) }
        set { store(newValue.bitPattern, ordering: .releasing) }
    }
}

var val = UnsafeAtomic<UInt64>.create(0)
val.double = 3.14
let x: Double = val.double
2 Likes

that’s what i ended up doing. it’s a shame swift-atomics can’t do this by default.

(but i didn’t expose a setter for the property, to prevent it from being passed inout.)

1 Like