Since your backing buffer is copy-on-write, you can do the in-place mutation
optimization in your immutable implementations, something like this:
class C {
var value: Int
init(value: Int) { self.value = value }
}
struct S { var c: C }
func addInts(x: S, y: S) -> S {
var tmp = x
// Don't use x after this point so that it gets forwarded into tmp
if isKnownUniquelyReferenced(&tmp.c) {
tmp.c.value += y.c.value
return tmp
} else {
return S(c: C(value: tmp.c.value + y.c.value))
}
}
which should let you get similar efficiency to the mutating formulation while
using semantically immutable values.
-Joe
> On Aug 5, 2016, at 2:35 PM, Fritz Anderson via swift-users
> <[email protected]> wrote:
>
> Swift 3 as of Xcode 8.0b4
>
> TL;DR: I have a struct value type backed by a copy-on-write mutable buffer.
> You use it to perform arithmetic on the buffers. The most expressive way to
> do this efficiently is to chain the arithmetic operators so each mutates the
> same buffer. Swift does not like to chain mutating operators — it treats the
> result of each step as immutable, so you can’t continue the chain. I can’t
> argue; the syntax apparently can't express anything else.
>
> All the alternatives I see are ugly-to-dangerous.
>
> Have I missed something, I hope? Please make a fool of me.
>
> — F
>
> The details of my use case or implementation are off-topic; even if mine are
> ill-considered, surely apt ones exist. Unless you can demonstrate there are
> none.
>
> The vDSP_* functions in Apple’s Accelerate framework are declared in C to
> operate on naked float or double pointers. I decided to represent such Float
> buffers in Swift by a struct (call it ManagedFloatBuffer) containing a
> reference to a FloatBuffer, which is a final specialization of class
> ManagedBuffer<Int, Float>.
>
> (The names are a work-in-progress. Just remember: ManagedFloatBuffer is a
> value type that can copy-on-write to a reference to FloatBuffer, a backing
> store for a bunch of Floats.)
>
> The nonmutating funcs:
>
> func subtract(_ other: ManagedFloatBuffer) -> ManagedFloatBuffer
> func subtract(_ scalar: Float) -> ManagedFloatBuffer
>
> are straightforward. They return new ManagedFloatBuffer values. You can chain
> further calls to simplify a complex calculation that is neither intricate nor
> tied up in temporaries:
>
> let sum²OfResiduals = speeds
> .subtract(cameraSpeed.mean)
> .multiply(feetToMeters)
> .sumOfSquares
>
> Great. And vDSP gets you about a 40% boost. (The compiler itself seems to do
> a pretty good job of auto-vectorizing; the unoptimized code is a couple of
> orders of magnitude slower.) But as you chain the immutables, you generate
> new FloatBuffers to hold the intermediate results. For long chains, you end
> up allocating new buffers (which turns out to be expensive on the time scale
> of vectorized math) and copying large buffers into them that you are about to
> discard. I want my Swift code to be as performant as C, but safer and more
> expressive.
>
> So how about some mutating functions to change a ManagedFloatBuffer’s bytes
> in-place (copying-on-write as needed so you can preserve intermediate values)?
>
> mutating func reduce(by other: ManagedFloatBuffer) -> ManagedFloatBuffer
> mutating func reduce(by scalar: Float) -> ManagedFloatBuffer
>
> These return self, because I’d hoped I could chain operators as I did with
> the non-mutating versions.
>
> The compiler doesn’t like this. It says reduce(by:) returns an immutable
> value, so you can’t chain mutating functions.
>
> (I can see an issue in that when the first func's self is copied as the
> return value that is used as the second func’s self, that could make two
> surviving references to the same buffer, so a buffer copy would happen when
> you mutate the second func’s self anyway. I’m not sure the compiler has to do
> that, but I can see how it might be hard to account for otherwise. Hey, it’s
> a tail call, right? SMOP, not source-breaking at all.)
>
> StackOverflow invites me to eat cake: Make the mutable operand inout to funcs
> I call one by one. Something like:
>
> multiply(perspectiveCorrections, into: &pixelXes)
> sin(of: &pixelXes)
> multiply(pixelXes, into: &speeds)
> multiply(feetToMeters, into: &speeds)
> subtract(cameraSpeed.mean, from: &speeds)
> let sumSquaredOfResiduals = speeds.sumOfSquares
>
> // grodiness deliberately enhanced for illustration
>
> I’d rather not. The thing to be calculated is named at the bottom of the
> paragraph. The intermediate steps must preserve names that change meaning
> line-by-line. You have to study the code to recognize it as a single
> arithmetic expression.
>
> And by the by, if a vector operand is itself the result of a mutating
> operation, the dependency graph becomes a nightmare to read — I can’t be sure
> the illustration even expresses a plausible calculation.
>
> Thinking up more reasons to hate this solution is a fun parlor game you and
> your family can play at home.
>
> Strictly speaking, the compiler is right: I don’t see any language construct
> that expresses that a returned value type that may be mutated by a chained
> func. Am I correct?
>
> I’m not at all happy with turning ManagedFloatBuffer into a class.
> Intuitively, this is a value type. Passing a packet of Floats into a func (or
> into another thread, as one does with math) and finding your Floats had
> changed in the mean time is… surprising.
>
> I’m not optimistic, but I have to ask: Is there a way to do this — to take
> mutability down an operator chain?
>
> — F
>
>
>
>
>
>
>
> _______________________________________________
> swift-users mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-users
_______________________________________________
swift-users mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-users