Go Home Swift Compiler, You’re Drunk

Swift is approaching its two year anniversary. Thus far it has experienced high adoption and continues to grow as developers transition away from Objective-C. As a programming language, Swift is great. It feels productive to work in, and has a nice variety of modern language features.

After going through some changes, the language and platform seem to be stabilizing (ignoring some Swift 3 caveats). At this point, it would seem that the Swift development experience should be smooth sailing in open waters, not navigating through a maze of icebergs. For the most part, I think this is true. However, I have also run into some icebergs.

Consider the following code:

let myCompany = [
   "employees": [
        "employee 1": ["attribute": "value"],
        "employee 2": ["attribute": "value"],
        "employee 3": ["attribute": "value"],
        "employee 4": ["attribute": "value"],
        "employee 5": ["attribute": "value"],
        "employee 6": ["attribute": "value"],
        "employee 7": ["attribute": "value"],
        "employee 8": ["attribute": "value"],
        "employee 9": ["attribute": "value"],
        "employee 10": ["attribute": "value"],
        "employee 11": ["attribute": "value"],
        "employee 12": ["attribute": "value"],
        "employee 13": ["attribute": "value"],
        "employee 14": ["attribute": "value"],
        "employee 15": ["attribute": "value"],
        "employee 16": ["attribute": "value"],
        "employee 17": ["attribute": "value"],
        "employee 18": ["attribute": "value"],
        "employee 19": ["attribute": "value"],
        "employee 20": ["attribute": "value"],

Copy the above code into XCode, press “build,” and go get a coffee. Come back in 12 hours. Yes, the above dictionary literal code takes at least 12 hours to compile. The compilation time grows roughly exponentially with each employee that is added. The good news is that this bug was submitted in December. The bad news is that it hasn’t been fixed yet, and strange issues like these can creep into your codebase and cause egregiously long compile times.

I stumbled across this issue when I noticed that our unit tests were taking a suspiciously long time to build. Eventually, it became annoying, so I decided to investigate. I was able to narrow the problem down to a single unit test that by itself was taking minutes to compile. The test wasn’t doing anything strange, just defining a JSON structure that we were testing our ability to parse. After some poking around the code, and internet searches, I came across the above bug report. It made me wonder how many other unit tests or code snippets we had that might be contributing to a bloated compilation time.

Be aware of Swift’s inability to efficiently parse and compile simple Dictionary/Array literals, at least until the problem is fixed, and keep an eye open for other icebergs.

For folks that find this post in the future, I used Swift 2.2 and XCode 7.3 in the above example. The output of xcrun swift -version is

Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
Target: x86_64-apple-macosx10.9

For those interested, I put the project I used to test/reproduce this issue on my Github page.

Update: Big thanks to everyone for the interesting discussion here, on Reddit, and on Hacker News. I have learned a lot about the complexities of type inference. Per the discussion, the best way to work around this problem is to explicitly define types for the dictionary literal (as Brian describes below). It also looks like a fix has been submitted for this issue (see Joe's comment below). The commit message for the fix is awesome!

  • Brian Pratt says:

    Hey Matt,

    Type inference is known to be a bit slow when inferring nested data structures. I think they’re hoping to speed it up before the release of Swift 3, but for now you can make the compilation faster by adding a type annotation:

    let myCompany: [String: [String: [String: String]]]

    Doing this sped it up to under a tenth of a second on my machine. Cheers, and good luck!

    • Benjamin says:

      Wow, this is really sad. The same code ported to Kotlin compiles in like no time with same level of type inference:

      val customers = mapOf(
      “employees” to mapOf(
      “employee 1” to mapOf(“attribute” to “value”),
      “employee 2” to mapOf(“attribute” to “value”),
      “employee 3” to mapOf(“attribute” to “value”),
      “employee 4” to mapOf(“attribute” to “value”),
      “employee 5” to mapOf(“attribute” to “value”),
      “employee 6” to mapOf(“attribute” to “value”),
      “employee 7” to mapOf(“attribute” to “value”),
      “employee 8” to mapOf(“attribute” to “value”),
      “employee 9” to mapOf(“attribute” to “value”),
      “employee 10” to mapOf(“attribute” to “value”),
      “employee 11” to mapOf(“attribute” to “value”),
      “employee 12” to mapOf(“attribute” to “value”),
      “employee 13” to mapOf(“attribute” to “value”),
      “employee 14” to mapOf(“attribute” to “value”),
      “employee 15” to mapOf(“attribute” to “value”),
      “employee 16” to mapOf(“attribute” to “value”),
      “employee 17” to mapOf(“attribute” to “value”),
      “employee 18” to mapOf(“attribute” to “value”),
      “employee 19” to mapOf(“attribute” to “value”),
      “employee 20” to mapOf(“attribute” to “value”)

      • Fred Fnord says:

        Imagine! Different languages have different problems!

      • Andrius Bentkus says:

        It is a bug, of course every other language will compile faster.

      • Brian Pratt says:

        Kotlin doesn’t have StringLiteralConvertibles, I assume, so that’s probably why.

      • Stephen Carman says:

        This is not even a remotely good comparison. These languages are compiled and ran in totally different ways.

      • nardi says:

        It’s not the same amount of type inference. In Kotlin, that can only be a Map<String, Map<String, Map>>. In Swift, you have StringLiteralConvertible and DictionaryLiteralConvertible, and those types can be anything which implements those protocols.

    • Nick says:

      You don’t even have to be that specific about types. Even Dictionary makes this fast again.

      • Nick says:

        Ah, getting escaped here. Let me try again: Dictionary<String, AnyObject>

    • Eimantas says:

      Adding `let myCompany: [String: AnyObject]` boosts compile time just the same. But switch that to just `AnyObject` and you can go on holidays for a day.

  • Brian Gesiak says:

    https://bugs.swift.org/browse/SR-305 was the best task tracking this problem that I could find. Let me know if you find other related issues–it’d be nice to deduplicate, and centralize information for people who may want to contribute to fixing this bug! :)

  • Andrew Theken says:

    I’m pretty sure I’ve seen this, and it was due to the trailing comma in the array/dictionary literals.

    • Mark J. Reed says:

      Nope, even without the trailing literal, the type-inference version takes exponential time. The explicitly-typed version does take less than 100ms, though.

  • Joe Pamer says:

    Thanks everyone! For what it’s worth, this is an area I’ve been working hard to clean up for Swift 3, and I just pushed a fix for this specific issue to the master branch. (2cdd7d64e1)

    • Matt Nedrich Matt Nedrich says:

      Thanks Joe, I appreciate you following up. Fantastic work in such short notice. When do you imagine that we will see this fix work it’s way into an official release?

      Also, big thanks to everyone for the interesting discussion. I have learned a lot about the complexities of type inference.

  • Greg Quinn says:

    Unfortunately still a problem in Xcode 8 GM.

    • Dan Leifker says:

      Yes, and Xcode 8 seems to be worse than previous versions. Our code compiled just fine with array literals under previous versions, but with Xcode 8 the build process grinds to a halt. Took us several hours to find and fix.

    • Although not fully quashed, it seems to be considerably better in Xcode 8 release (8.0 / 8A218a).  Testing out a new project with the only modifications being adding the 20-employee `myCompany` to `applicationDidFinishLaunching()` and adding `-Xfrontend -debug-time-function-bodies` to the Other Swift Flags build setting, I’m seeing a build time of 135.1ms for the `applicationDidFinishLaunching()` function on my 2009 2.66GHz quad Nehalem i7 Mac Pro.

      • Actually, if I change it to `let myCompany:[String:[String:[String:String]]] = …` the function build time drops only 2.5× to 54.1ms.  I’d call that as fixed as it needs to be.

  • Laura Dickey says:

    I am still seeing this in XCode 8 (8A218a). A fully qualified static dictionary literal is grinding compile times to a halt if I go over about 8 entries. I’m going to move the data to a plist and read it in at runtime to avoid fighting with the compiler at this point.

    Example: let centers : [String : CGPoint] = [ “point1” : CGPoint(0.5, 0.5), “point2” : CGPoint(0.2,0.3) ]

    (except with about 14 entries, but that’s the type of the declaration)

  • BrianHenryIE says:

    I just had a similar problem where String concatenation was stalling the compiler. I was using multiline `+ =”…` and on one line I had no space: `+=”…`, once I removed that things compiled as normal.

  • Anand says:

    Am still observing this issue in Xcode Version 8.3.3 wehn i try to compile
    let string = “!*'();:@&=+$,/?%#[]” as? CFString
    What works for me is
    let charsTobeEscaped = “!*'();:@&=+$,/?%#[]”
    let escapedLiteral:CFString = charsTobeEscaped as NSString

  • Comments are closed.