Speeding up Array’s Sorting in Swift

Peter Wood in Swift
22 Aug 2014, 15:17

What’s up?

In my previous post I told you about the fix for compilation error in case optimization was enabled. And as I’ve already mentioned, I still haven’t succeeded in improving app’s performance. Today I’ll tell you how I found the reason for the poor performance and how I fixed it.

In my project, the list of files sorted by name should be displayed. When lists are sorted via string comparator, the performance is good enough. But if the files are sorted like in the Finder, performance drops dramatically in case array contains a big amount of elements.

Here are some examples:

Let’s take the contents of ~/Library/Preferences folder (1000 files in total) and sort the list with this code:

 filesArray.sort({ (item1:NSURL, item2:NSURL) -> Bool in               let comparisonOptions:NSStringCompareOptions = NSStringCompareOptions.CaseInsensitiveSearch | NSStringCompareOptions.NumericSearch | NSStringCompareOptions.WidthInsensitiveSearch | NSStringCompareOptions.ForcedOrderingSearch;               var str1:String = item1.path!.lastPathComponent             var str2:String = item2.path!.lastPathComponent         	             var res = str1.compare(str2, options: comparisonOptions, range: str1.startIndex ..< str1.endIndex, locale: NSLocale.currentLocale())         	             return res == NSComparisonResult.OrderedAscending         })  

When using Xcode6 beta6 with the enabled optimization, it takes 5 seconds to sort the files. (Just to compare, it took about several minutes in beta5.) As you see, files sorting speed cannot be called acceptable anyway.

I decided to compare the sorting time of the method described above with the time of this method:

filesArray.sort( { $0.path!.lastPathComponent > $1.path!.lastPathComponent })  

And it suddenly dawned on me that the problem could lie in calling the following Objcetive-C method from Swift too often:

 - compare:options:range:locale:

I decided to sort array using methods of NSArray class.

       var nsFilesArray = filesArray as NSArray     	         nsFilesArray = nsFilesArray.sortedArrayUsingComparator({(item1:AnyObject!, item2:AnyObject!) -> NSComparisonResult in             let comparisonOptions:NSStringCompareOptions =  NSStringCompareOptions.CaseInsensitiveSearch |  NSStringCompareOptions.NumericSearch |  NSStringCompareOptions.WidthInsensitiveSearch |  NSStringCompareOptions.ForcedOrderingSearch;               var str1:String = (item1 as NSURL).path!.lastPathComponent             var str2:String = (item2 as NSURL).path!.lastPathComponent               return str1.compare(str2, options: comparisonOptions, range: str1.startIndex ..< str1.endIndex, locale: NSLocale.currentLocale())         })     	         filesArray = nsFilesArray as Array  

These changes result in sorting within only 0,02 seconds even when optimization is disabled. That is what I call adequate performance.

Note: This might prove to be not the best solution actually, as I had to call NSArray method.

Hope to come back to this issue after Xcode6 release. But at the moment, that is the most satisfactory resolving of the problem.

BTW, see in the last listing how closure can be easily transferred into the ordinary Objective-C method!