Swift v1.2 Performance

In a series of posts I wrote a few weeks back, I looked at the performance of Swift in the context of debug (e.g. non-optimized) builds. The developer workflow pain that I was having was significant.

Of course, today a new build of Xcode dropped that finally (I jest a little) contained a new build of Swift: Swift v.1.2.

So, is it any better?

I've spent about ten minutes playing around with my projects (described in the blog posts mentioned above). I ran into the following issues:

  • Xcode crashed opening a source file (/sigh)
  • Swift v1.2 conversion missed updating two cases:
  • Byte was removed and not automatically changed to UInt8 in the

    initializer case.

  • UnsafeMutablePointer<Pixel>.null() was not replaced to it's proper usage:

    let pixels: UnsafeMutablePointer<Pixel> = nil.

The crash was annoying. However, the Swift conversion process went well and I don't really care that they missed those two cases.

So…?

SO MUCH BETTER!! BUT…

The ObjC optimized RenderWeirdGradient() function looked like this:

void RenderGradient(RenderBufferRef buffer, int offsetX, int offsetY)
{
    int width = buffer->width + offsetX;
    int height = buffer->height + offsetY;
    uint32_t *pixel = (uint32_t *)&buffer->pixels[0];

    for (int y = offsetY; y < height; ++y) {
        for (int x = offsetX; x < width; ++x) {
            *pixel = 0xFF << 24 | (x & 0xFF) << 16 | (y & 0xFF) << 8;
            ++pixel;
        }
    }
}

The timings I saw on my machine here:

-O0   0.038723s
-Os   0.010972s

I tested against three different Swift loops:

Raw UnsafeMutablePointer

func RenderGradient(var buffer: RenderBuffer, offsetX: Int, offsetY: Int)
{
    var offset = 0
    for (var y = 0, height = buffer.height; y < height; ++y) {
        for (var x = 0, width = buffer.width; x < width; ++x) {
            let pixel = Pixel(
                red: 0,
                green: UInt8((y + offsetY) & 0xFF),
                blue: UInt8((x + offsetX) & 0xFF),
                alpha: 0xFF)
            buffer.pixels[offset] = pixel;
            ++offset;
        }
    }
}

Pixel Array

func RenderGradient(inout buffer: RenderBuffer, offsetX: Int, offsetY: Int)
{
    // I truly hope you have turned down the number of iterations or you have picked
    // up a new build of Swift where this is not dog slow with -Onone.
    var offset = 0
    for (var y = 0, height = buffer.height; y < height; ++y) {
        for (var x = 0, width = buffer.width; x < width; ++x) {
            let pixel = Pixel(
                red: 0,
                green: UInt8((y + offsetY) & 0xFF),
                blue: UInt8((x + offsetX) & 0xFF),
                alpha: 0xFF)
            buffer.pixels[offset] = pixel;
            ++offset;
        }
    }
}

Pixel Array with UnsafeMutablePointer

func RenderGradient(inout buffer: RenderBuffer, offsetX: Int, offsetY: Int)
{
    buffer.pixels.withUnsafeMutableBufferPointer { (inout p: UnsafeMutableBufferPointer<Pixel>) -> () in
        var offset = 0
        for (var y = 0, height = buffer.height; y < height; ++y) {
            for (var x = 0, width = buffer.width; x < width; ++x) {
                let pixel = Pixel(
                    red: 0,
                    green: UInt8((y + offsetY) & 0xFF),
                    blue: UInt8((x + offsetX) & 0xFF),
                    alpha: 0xFF)
                p[offset] = pixel
                ++offset;
            }
        }
    }
}

With -Onone, the timings are (the diff % is compared against the optimized ObjC code presented above):

Swift v1
Swift Rendering Tests: 30 iterations per test
---------------------
UnsafeMutablePointer<Pixel>     avg time: 0.186799s
[Pixel]                         avg time: 27.9754s
[Pixel].unsafeMutablePointer()  avg time: 1.18535s
Swift v1.2
Swift Rendering Tests: 30 iterations per test
---------------------
UnsafeMutablePointer<Pixel>     avg time: 0.185078s, stddev: 0.00442746s, diff: -377%
[Pixel]                         avg time: 3.92371s, stddev: 0.0777589s, diff: -10032%
[Pixel].unsafeMutablePointer()  avg time: 0.518955s, stddev: 0.00921328s, diff: -1239%

With -O, the timings are:

Swift v1
Swift Rendering Tests: 30 iterations per test
---------------------
UnsafeMutablePointer<Pixel>     avg time: 0.0223397s
[Pixel]                         avg time: 0.0287606s
[Pixel].unsafeMutablePointer()  avg time: 0.0402743s
Swift v1.2
Swift Rendering Tests: 30 iterations per test
---------------------
UnsafeMutablePointer<Pixel>     avg time: 0.022574s, stddev: 0.00115951s, diff: -105%
[Pixel]                         avg time: 0.0268918s, stddev: 0.00143757s, diff: -144%
[Pixel].unsafeMutablePointer()  avg time: 0.0414112s, stddev: 0.00207772s, diff: -276%

There are few large takeaways here:

  1. The raw UnsafeMutablePointer<Pixel> is still significantly faster, in all

scenarios; no performance change between Swift versions. 2. The [Pixel] has significantly better performance than before. It seems

the optimized build even got slightly faster. HOWEVER, the array solution

is still not good enough as it can only render at about 7 to 8Hz (target is

30Hz). 3. Keeping the [Pixel] array and using the unsafeMutablePointer() call for

"hot areas" of your code has also gotten much better. It now gets us to the

target 30Hz in non-optimized builds, but still nearly 3x slower than

simply using an UnsafeMutablePointer<Pixel> to begin with.

My key takeaway:

Swift is getting better and faster but it's still not suitable in the domain

space I'm currently working in for this project: high-performance, real-time

systems.

Eagerly awaiting Swift vNext!

Swift v1.2 Performance