I see some things that might help:
index(_:, offsetBy:) is O(n) where n is the amount you're offsetting, so you can squeeze a bit out of calculating the
endIndex as an offset from the
startIndex especially if you're getting substrings from near the end of the string:
let startIndex = self.index(self.startIndex, offsetBy: start)
let endIndex = self.index(startIndex, offsetBy: end-start)
The other option could be to use the
UTF16 view of the string instead of the character view.
UTF16Index has an option to create directly from an Int (rather than) offsetting an index which will be faster (I assume O(1) but I can't find a reference).
let start16 = String.UTF16Index(start)
let end16 = String.UTF16Index(end)
The consideration around the UTF16 approach depends if you're going to be using text that has multi-width characters and how you're getting the actual start and end values to make the substring. Here's a post that I found useful getting my head around these options: https://oleb.net/blog/2016/08/swift-3-strings/
So it looks like there is an opportunity for a little bit of a performance increase there!
When you check to see if the string is empty you're checking if the character count is zero.
self.characters.count == 0 is O(n) where n is the number of characters, you can get some performance increase here by using
self.isEmpty which is O(1).
edit: added 4th option that returns
Finally, with the UTF16 option there's the need to cast to a String and force unwrap if you want to return the type
String. An alternative could be to return
nil instead as your early exit:
I ran a quick profile in Xcode comparing those four options:
1. Baseline approach (in question)
2. Alternative (use isEmpty and calculate endIndex from startIndex)
3. UTF16 (use isEmpty and create UTF16 index directly from Int)
4. UTF16 nil (use isEmpty, create UTF16 index from Int and return String?)
Benchmark substring using
1. Baseline -> 1.151s (2% STDEV)
2. Alternative -> 0.633s (1% STDEV)
3. UTF16 -> 0.408s (2% STDEV)
4. UTF16 nil -> 0.404s (1% STDEV)
Benchmark early exit using
1. Baseline -> 0.074s (4% STDEV)
2. Alternative -> 0.024s (12% STDEV)
3. UTF16 -> 0.024s (11% STDEV)
4. UTF16 nil -> 0.019s (12% STDEV)
Here's a gist of the test I used for full transparency: https://gist.github.com/mathewsanders/c4c43915c5e1c13e8fe3b912bf4c27d1
So absolutely use
isIndex instead of counting characters, and maybe consider using the UTF16 view if it's appropriate for the text you'll be making substrings from.
It also looks like returning
nil instead of an empty string gives a bit of a boost, but you might loose that small advantage elsewhere in your code depending on how you handle the nil` return.