Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Type Checking Takes Too Long on Very Short Function

Tags:

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)

like image 410
Nicolas Miari Avatar asked Jun 22 '17 07:06

Nicolas Miari


People also ask

How does time type checking work in Swift?

Compile time type checking (or static type checking) operates on the Swift source code. The Swift compiler looks at explicitly stated and inferred types and ensures correctness of our type constraints. Here’s a trivial example of static type checking:

How to check if an object is of given type in Swift?

In order to check if an object is of given type in Swift, you can use the type check operator is. Given an example class Item , you can check if the object is of its type like that: Same applies to built-in data types in Swift. For example, if you’d like to check if given object is a String or Int:

Why does it take so long to compile in Swift?

This is a type inference problem. The Swift compiler doesn’t know what type is coming next so it has to investigate and find out before it can continue compilation. This case is a particular “quirk” where adding more data increases compile time exponentially but fundamentally Swift is doing The Right Thing: checking which type it thinks you mean.

What is the difference between static and dynamic type checking in Swift?

Static type checking occurs at compile time and dynamic type checking happens at run time. Each of these two stages come with a different, partially incompatible, toolset. Compile time type checking (or static type checking) operates on the Swift source code.


1 Answers

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 ==.

like image 56
Scott McKenzie Avatar answered Nov 15 '22 17:11

Scott McKenzie