Nicolas Miari
Nicolas Miari

Reputation: 16246

Swift Type Checking Takes Too Long on Very Short Function

I have set the Swift compiler flag -warn-long-function-bodies to 90 milliseconds, to see which functions in my project are taking too long to compile (due to type checking).

I have the following method:

func someKey(_ sectionType: SectionType, row: Int) -> String {
    let suffix = row == 0 ? "top" : "content"
    return "\(sectionType)_\(suffix)"
}

(SectionType is an String-backed enum)

As it is above, it takes 96ms on a 2017 MacBook Pro. The first thing I tried is to circumvent string interpolation and use \(sectionType.rawValue) instead of \(sectionType), but now it gives me 106 ms. Wrong move...

Next, I changed:

let suffix = row == 0 ? "top" : "content"

to:

let suffix = "top"

The warning goes away, so it is the ternary operator that is causing trouble.

I tried this instead:

let suffix: String = { // Note the type annotation!
    if row == 0 {
        return "top"
    }
    return "content"
}()

...but now it is the closure that takes 97 ms (the whole function, 101).

I even tried the more explicit:

    let suffix: String = {
        if row == 0 {
            return String("top")
        } else {
            return String("content")
        }
    }()

...and I get closure: 94ms; function: 98ms.

What's going on?

Is my 90-milliseconds limit too low? I know there was (is?) a type-checking bug with dictionary literals, but this seems something entirely different...?

My environment is Xcode 8.3.2 (8E2002), Swift: Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42)


But Wait! There's more...

I tried this function body:

func someKey(_ sectionType: SectionType, row: Int) -> String {
    if row == 0 {
        return "\(sectionType.rawValue)_top"
    } else {
        return "\(sectionType.rawValue)_content"
    }
}

...and it takes 97ms~112ms!?


Addendum: I transplanted the function and enum to a clean, minimal project (Single View Application) set up the same warning but it does not happen. I'm sure the project as a whole is affecting this one method somehow, but can't quite figure how yet...


Addendum 2: I tested the static version of my function: use fixed suffix "top" regardless of the value of row(this takes less than 90 ms and triggers no warning), but added the following if block:

func someKey(_ sectionType: SectionType, row: Int) -> String {
    if row == 0 {
        print("zero")
    } else {
        print("non-zero")
    }

    let suffix: String = "top"
    return "\(sectionType)_\(suffix)"
}

This takes me back to 96~98 ms! So the problem arises when comparing row to zero?


Workaround: I kept playing with my code and somehow discovered that if I replace the if block with a switch statement, the issue goes away:

func someKey(_ sectionType: SectionType, row: Int) -> String {
    let suffix: String = {
        switch row {
        case 0:
            return "top"
        default:
            return "content"
        }
    }()
    return "\(sectionType)_\(suffix)"
}

(I won't answer my own question because I do not consider this an explanation of what's really going on)

Upvotes: 25

Views: 9436

Answers (1)

Scott McKenzie
Scott McKenzie

Reputation: 16242

I think it's the ternary operator.

I had similar results in Xcode 11 (~93ms) but the compile time decreases to ~23ms with:

func someKey(_ sectionType: SectionType, row: Int) -> String {

    var suffix = "top"

    if row != 0 {
        suffix = "content"
    }

    return "\(sectionType)_\(suffix)"
}

By changing the logic on this line, I think we can prove it's the ternary logic because the method reduces to ~1ms. I've just made row a Boolean.

func someKey(_ sectionType: SectionType, row: Bool) -> String {
    let suffix = row ? "top" : "content"
    return "\(sectionType)_\(suffix)"
}

Equally (no pun intended) changing the ternary logic to let suffix = row != 0 ? "top" : "content" halves the compile time. Which is comparable to my first code block. != is faster for Swift to understand than ==.

Upvotes: 5

Related Questions