Swift Seems to Be Slower as Objective-C in Loops

Slow Swift Arrays and Strings performance

There are multiple reasons why the Swift code is slower than the Objective-C code.
I made a very simple test case by comparing two fixed strings 100 times.

  • Objective-C code: 0.026 seconds
  • Swift code: 3.14 seconds

The first reason is that a Swift Character represents an "extended grapheme cluster",
which can contain several Unicode code points (e.g. "flags"). This makes the
decomposition of a string into characters slow. On the other hand, Objective-C
NSString stores the strings as a sequence of UTF-16 code points.

If you replace

let a = Array(aStr)
let b = Array(bStr)

by

let a = Array(aStr.utf16)
let b = Array(bStr.utf16)

so that the Swift code works on UTF-16 sequences as well then the time goes down
to 1.88 seconds.

The allocation of the 2-dimensional array is also slow. It is faster to allocate
a single one-dimensional array. I found a simple Array2D class here:
http://blog.trolieb.com/trouble-multidimensional-arrays-swift/

class Array2D {
var cols:Int, rows:Int
var matrix: [Int]

init(cols:Int, rows:Int) {
self.cols = cols
self.rows = rows
matrix = Array(count:cols*rows, repeatedValue:0)
}

subscript(col:Int, row:Int) -> Int {
get {
return matrix[cols * row + col]
}
set {
matrix[cols*row+col] = newValue
}
}

func colCount() -> Int {
return self.cols
}

func rowCount() -> Int {
return self.rows
}
}

Using that class in your code

func levenshtein(aStr: String, bStr: String) -> Int {
let a = Array(aStr.utf16)
let b = Array(bStr.utf16)

var dist = Array2D(cols: a.count + 1, rows: b.count + 1)

for i in 1...a.count {
dist[i, 0] = i
}

for j in 1...b.count {
dist[0, j] = j
}

for i in 1...a.count {
for j in 1...b.count {
if a[i-1] == b[j-1] {
dist[i, j] = dist[i-1, j-1] // noop
} else {
dist[i, j] = min(
dist[i-1, j] + 1, // deletion
dist[i, j-1] + 1, // insertion
dist[i-1, j-1] + 1 // substitution
)
}
}
}

return dist[a.count, b.count]
}

the time in the test case goes down to 0.84 seconds.

The last bottleneck that I found in the Swift code is the min() function.
The Swift library has a built-in min() function which is faster. So just removing
the custom function from the Swift code reduces the time for the test case to
0.04 seconds, which is almost as good as the Objective-C version.

Addendum: Using Unicode scalars seems to be even slightly faster:

let a = Array(aStr.unicodeScalars)
let b = Array(bStr.unicodeScalars)

and has the advantage that it works correctly with surrogate pairs such
as Emojis.

Is Swift really slow at dealing with numbers?

Here are optimization levels for the Swift compiler's code generation (you can find them in Build Settings):

[-Onone] no optimizations, the default for debug.
[-O] perform optimizations, the default for release.
[-Ofast] perform optimizations and disable runtime overflow checks and runtime type checks.

Using your code I got these times at different levels of optimization:

[-Onone]

Swift: 6110.98903417587ms
Objc: 134.006023406982ms

[-O]

Swift: 89.8249745368958ms
Objc: 85.5680108070374ms

[-Ofast]

Swift: 77.1470069885254ms
Objc: 76.3399600982666ms

Keep in mind that -Ofast is comes with risks. e.g. It will silently ignore integer and array overflows, producing nonsense results, so if you choose to use it you'll have to guarantee yourself that overflows aren't possible in your program.

Whats the cause, Swift is supposed to be that much faster than Objective-C?

This is largely speculation (although informed speculation) but my two big theories are:

1) No Reduced dynamic method dispatch. Every method call in Objective-C goes through objc_msgSend. In the fastest case, this can be as quick as 16 instructions, but it can also be a lot slower. Swift will incur this penalty in fewer situations than Objective-C will, for instance: method calls to swift-only protocol methods do not hit objc_msgSend, but if the protocol is declared in Obj-C, or if the swift protocol is decorated with @objc (such that it can be adopted by Objective-C objects as well), then method calls to methods in that protocol adopted by swift objects appear to be dispatched via objc_msgSend.

2) Avoiding heap allocations. In Objective-C, (effectively) every object is heap allocated. With a static type system, the compiler can infer more about the lifecycle of an object and allocate it on the stack unless it has to cross the Objective-C boundary (or is too big to be allocated on the stack).

I suspect that #2 is the much bigger of these two, but both are likely significant contributors. I'm sure there's more to it than just this, but these are two very likely contributors.

Accessing object-c NSDictionary values in Swift is slow

A part of the problem is that bridging NSDictionary<NSString *, Part *> to [String: Part] involves runtime checks for all keys and values of the dictionary. This is needed because the Objective-C generics arguments for NSDictionary don't guarantee that the dictionary won't held incompatible keys/values (for example Objective-C code could add non-string keys or non-Part values to the dictionary). And for large amounts of dictionaries this can become time consuming.

Another aspect is that Swift will also likely create a corresponding dictionary to make it immutable, since the Objective-C one might as well be a `NSMutableDictionary'. This involves extra allocations and deallocations.

Your approach of adding a partFor() function avoids the above two by keeping the dictionary hidden from the Swift world. And it's also better architecturally speaking, as you are hiding the implementation details for the storage of the car parts (assuming you also make the dictionary private).

Terribly Slow migrated Objc to swift code

It appears my issues were due to a debug build. In debug build there is no optimization. Once you do a run build the optimization kicks in and things run MUCH MUCH faster....

Similar to the answer posted here: Is Swift really slow at dealing with numbers?

Swift is 1000's of times slower than C and Python code?

The Swift compiler assumes you don't care how slow the resulting code runs unless you request some compiler optimization, such as by setting a compiler flag, such as -Ofast

Otherwise the Swift compiler seems to add a metric ton of extra and slow machine code to the compiled result, possibly to help debugging.

Why `String(describing: Class.self)` is slower than `NSStringFromClass(Class.self)`?

You’re comparing apples with motorcars.

  • NSStringFromClass does a highly specific job, involving only class types, and is used for a conversion related to the dynamic nature of Objective-C programs.

  • String(describing:) in Swift turns anything into a string representation and is intended (and suitable) only for debugging. It will not be used for any user-facing code and no meaningful program action will depend upon its output. Thus it will not appear in a released app and its speed is of no account. Your question therefore optimizes not only prematurely but unnecessarily; the speed of String(describing:) is unimportant.



Related Topics



Leave a reply



Submit