back

by lerno·11y ago·view on hn ↗
We've essentially been told to use Swift arrays as we would C arrays, so your analogy is flawed. Another premise for Swift is that it should be usable for low level, high performance programming but "safer than C". This is something we don't see in practice.

Furthermore, using UnsafeMutablePointers in Swift makes the program a magnitude larger than the corresponding C program, with no additional gain in perf, readability or safety. It is a last resort, not something you should use all of the time.

Consequently I find it hard to agree with you.

2 comments
It's odd that you said "We've essentially been told ...". Will Apple reject apps using C arrays or something?

In any case, I don't really see the problem here. Every feature of every language has some kind of performance trade off. And it's natural to expect higher level abstractions in higher level languages will have some amount of performance overhead versus lower level abstractions.

In this case, there's no shortage of alternatives, so pick one and get on with it. If it ends up being too slow, go back and look at the alternatives and swap out Swift arrays for C arrays or NSArray or whatever.

No, but we have been told that the performance characteristics of the native Swift array should be the same as an array in C.

Judging from what the compiler team at Apple regularly write in forums and on twitter, it seems that they consider the performance issues for arrays (actually, this happens in other circumstances as well) to be a very real issue.

When you say "swap for a C array", then I suppose that you mean we allocate a chunk of memory like in C, then access it using unsafe methods (as there is no "C array" in Swift). Unfortunately, working with UnsafeMutablePointer and friends is extremely verbose and clumsy, which makes it an extremely bad experience.

Isn't there toll free bridging between a Swift Array and an NSArray? It would make a lot of sense for the performance characteristics of the two being the same if that were the case.

Yeah and swapping to a C array isn't that simple in Swift. It seems easier to just have objective c code and call that from Swift when needed.

Actually, a Swift array is pretty much identical to a C array. You get an unsafe pointer to that array and treat it exactly as it was one. NSArray on the other hand is toll-free bridged to a CFArray.

The current "solution" is to keep the array a Swift array, then in any performance critical code simply cast that into an unsafe pointer and work with that directly as if it was C array.

There's bridging, but it's not toll-free. You have to convert between them, which is pretty slow. Toll-free bridging means that no conversion is necessary, e.g. you can take a CFArray and just treat it like an NSArray and everything still works with no conversion.
Conversion may or may not be slow, depending on the elements involved. Specifically, if you're converting from Array<T> to NSArray where T is a class of @objc protocol type, it's guaranteed to be O(1). Converting from other types (such as Array<Int>) will do an O(n) copy, as it has to bridge each element (e.g. Int would bridge to NSNumber).

Conversely, treating any NSArray as an Array is always just a call to -copyWithZone (which is O(n) for mutable arrays and O(1) for immutable arrays), although the docs say that upon the first element access it type-checks the elements of the array (though presumably converting to [AnyObject] is always free).

The conversion is only supposed to happen once, but there was a bug (don't know if it is fixed yet, it was discovered quite a while ago), which made the Swift <-> ObjC NSDictionary conversion to be performed each time an access was made.
Can you give some more information? This is the first I've heard of that. The only thing that comes to mind that would explain this sort of behavior is if you actually make a copy of the Dictionary first and then access that copy; since it's a copy, the conversion would presumably invoke the normal copy-on-write behavior and make a copy of the backing storage, while leaving the original Dictionary un-converted. In that case, any subsequent copy + access would have to re-convert. And if that's what's going on, then that sounds like expected behavior, not a bug.

An example of what I'm talking about would be something like

  let dict = self.someDictionary // dict is a copy, not a reference
  let x = dict["foo"] // this would then convert
  let y = dict["bar"] // but this wouldn't
If you mean that the `let y = dict["bar"]` would convert as well, then I'm a bit skeptical, because it seems implausible that the conversion code in Dictionary would even be capable of "converting" a native storage to a native storage (i.e. it's reasonable to expect that the conversion code explicitly converts an NSDictionary to a native storage).
> Every feature of every language has some kind of performance trade off.

That's simply untrue (unless meant as a pointless tautology :)). There are plenty off free features and abstractions. From syntactic sugar things, to really advanced stuff like Rust's borrow checker. Even using lambdas in Rust can be cost free. (It almost seems like Rust and LLVM have created the sufficiently smart compiler.)

And even really high level languages like Haskell can do surprisingly great optimizations, so that, say, a pipeline of projections and filters costs the same as just writing the loop by hand.

For a safe array, the usually acceptable overhead is one bounds check per loop/sequence, right?

(Anyways, the article discusses non optimized builds, at which point nothing's guaranteed to be cheap, so he's really asking for more developer support tools.)

> told to use Swift arrays as we would C arrays

Until you can declare a fixed-length Swift array, or a Swift array that isn't copy-on-write by default, or a fixed-length Swift array that doesn't live on the heap, this can't possibly be true, no matter what you think you've been told.

> it should be usable for low level, high performance programming but "safer than C".

With optimizations turned on, the 'Swift []' code comes within 3x of the performance of the ObjC code. Lots of room for improvement, but not abysmal either.

> Furthermore, using UnsafeMutablePointers in Swift makes the program a magnitude larger than the corresponding C program...It is a last resort, not something you should use all of the time.

I agree.

From what we're told, copy-on-write doesn't happen unless it has to (i.e. the compiler discovered that we use it elsewhere).

In any case, the issue here with debug builds does not seem to be due to memory access nor copy-on-write, but simply because of ARC sprinkling the accesses to the array with release/retain pairs.

The reason it works ok with optimizations turned on, is because Swift will not remove unnecessary release-retain in -Onone.

This was not a great problem in ObjC, since it only affected ObjC method dispatch - which you were unlikely to do in a tight loop. Plus, ObjC ARC actually emits retain/release pairs differently.

The issue with ARC emits was discovered with the very first beta, but as of yet there has been no fix.

The problem here is probably this:

1. In order to perform a local optimization on ARC retain/release, even at -Onone, you need to change the retain/release emits to the style used by ObjC.

2. But that retain/release often incurs an unnecessary autorelease, which would prevent even an optimized Swift build from eliminating certain retain/releases.

In other words, they either get significantly better -Onone performance, or slightly worse -O/-Ounchecked performance.

That's quite plausible.