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-CNSString
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 ofString(describing:)
is unimportant.
Related Topics
Convert String to Nsdate in Swift
How to Customize the Font and Appearance of a Uialertcontroller in the New Xcode W/ iOS8
Swift Nsusernotification Doesn't Show While App Is Active
Using Codable to Encode/Decode from Strings to Ints with a Function in Between
Moving Skspritenode to Location of the Touch
Type a Requires That Type B Be a Class Type Swift 4
Setting Nsunderlinestyle Causes Unrecogognized Selector Exception
How to Properly Check If Non-Optional Return Value Is Valid
How to Delete Item from Collection View
Swift 2: Invalid Conversion from Throwing Function of Type to Non-Throwing Function
How to Get Core Data Entity by It's Objectid
What's Wrong with My #If Target_Os_Simulator Code for Realm Path Definition
Swift - Kvo - Change.Newvalue and Change.Oldvalue Are Nil
Ios-Charts Set Maximum Visible X Axis Values
Deep Copy of Cmimagebuffer or Cvimagebuffer
Why Upload Alamofire Background Request Don't Executes in Background