Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the runtime cost of Swift's casts?

What are the different runtime costs incurred by the following type casts?

  1. Numeric cast of constant, e.g.:

    let f = 0.1 as CGFloat
    

    I'd imagine this has zero runtime cost.

  2. Numeric cast of runtime value, e.g.:

    let f = someDoubleValue as CGFloat
    

    I'd imagine this has an extremely small runtime cost.

  3. Upcast, e.g.:

    let dict: [String: Int] = ...
    let anyObj = dict as AnyObject
    

    I'd expect this to have zero runtime cost.

  4. Failable Downcast, e.g.:

    let anyObj: AnyObject = ...
    if let str = anyObj as? String { ... }
    

    I'd expect this to have a runtime cost proportional to the number of classes in the hierarchy of the dynamic type of anyObj.

  5. Forced Downcast, e.g.:

    let anyObj: AnyObject = ...
    let str = anyObj as! String
    

    Maybe the cost for a forced downcast is slightly lower?

  6. Forced Downcast of collection, e.g.:

    let dates: [AnyObject] = ...
    for date in dates as! [NSDate] { ... }
    

    What happens here — especially when dates comes from an NSArray? Is the runtime cost of this cast proportional to the number of its elements? What if I cast to a more complex collection type like [String: [String: [Int]]] — is the whole collection traversed to make sure all its elements and subelements conform to this cast?

For each of the first four cases, are my assertions true?

like image 965
Jean-Philippe Pellet Avatar asked Feb 19 '15 09:02

Jean-Philippe Pellet


2 Answers

  • It is O(1) (almost 0) if it is obviously castable(like numeric casting and upcasting): case 1, 2, 3.

  • For other non-collection castings, it is obviously O(1): case 4, 5.

  • For collection downcastings:

    • as? is O(n) because element type checking is performed eagerly.
    • Native forced downcasting is O(1) because element type checking is deferred: case 6.
    • Bridged forced downcasting(NSArray as! [NSDate]) is O(n) because element type checking is performed eagerly.
    • Nested collections are recursively casted so you just apply the above rules recursively.

Sources:

  1. https://github.com/apple/swift/blob/master/stdlib/public/runtime/Casting.cpp
  2. https://github.com/apple/swift/blob/master/stdlib/public/core/ArrayCast.swift
  3. https://github.com/apple/swift/blob/master/stdlib/public/core/HashedCollections.swift.gyb
like image 173
an0 Avatar answered Nov 13 '22 18:11

an0


Just wanted to add that the runtime cost of protocol typecast is horrific. I tried to look at it in disassembly view and simply lost track of what's happening. So a line like this:

(self as! SomeProtocol).someMethod()

results in retain/release (which involve memory barriers), then there's a call to the old good objc_msgSend() (!) but not related to someMethod(), I presume because one of self or SomeProtocol are derived from class, and then a very very long chain of function calls with some loops involved. Like I said I lost patience trying to debug this single line of code in disassembly view.

While protocols are a really nice abstraction, they should be used sparingly in performance critical code.

like image 2
mojuba Avatar answered Nov 13 '22 19:11

mojuba