Swift string interpolation produces string that takes very long to insert in a dictionary

Under some conditions, string interpolation produces a string that is way slower to use as a dictionary key than a string produced with String(format:). Take the following code:
Code Block
var map = [String: String]()
let start = Date()
for _ in 0..<100000 {
    var s = "asdf"
    s = (s as NSString).appendingPathComponent("")
    s = transform(s)
    s = (s as NSString).substring(from: 1)
    map[s] = s
}
print(-start.timeIntervalSinceNow)
func transform(_ s: String) -> String {
    return "\(s)/\(s)"
//    return String(format: "%@/%@", s, s)
}

On my Mac I get the time interval 0.69 seconds printed out in the console (when using the string interpolation), but when commenting out line 13 and uncommenting line 14 (so that we use String(format:)) I get a 0.33 seconds time interval, less than half the time. Curiously, whenever uncommenting line 5 or line 7, string interpolation is faster.

This took me quite a lot of time to figure out, since I would expect both methods to produce the same kind of string, but string interpolation to be always faster. Does anybody know why?
Answered by OOPer in 627710022

What still doesn't make sense is that the two variants inside the transform() function both use a Swift String, still one is faster to insert into the dictionary than the other.

One uses NSString, and the other uses only pure Swift String.

I wrote:

(Please remember String(format: "%@/%@", s, s) considered as String(format: "%@/%@", s as NSString, s as NSString).)

But it was a little bit inaccurate.

Some of the methods in Swift String are just calling the equivalent methods of NSString.

String(format: "%@/%@", s, s) is equivalent to something like NSString(format: "%@/%@", s as NSString, s as NSString) as String.

I have confirmed that I can observe the same tendency with your code.

whenever uncommenting line 5 or line 7, string interpolation is faster.

That is exactly the behavior I expected.

In Swift String, as NSString is not a 0-cost operation.
It needs to hold some internal state for bridging to NSString, and such state is constructed on demand when you first write as NSString,
or when Swift compiler applies as NSString implicitly.
(Please remember String(format: "%@/%@", s, s) considered as String(format: "%@/%@", s as NSString, s as NSString).)

It is hard to predict exactly when such making String ready for bridging operation would happen, but your current code measures the time including such operations.

Code Block
var map = [String: String]()
let start = Date()
var s = "asdf"
s = (s as NSString).appendingPathComponent("")
var t = ""
for _ in 0..<100000 {
t = transform(s)
}
s = (t as NSString).substring(from: 1)
map[s] = s
print(-start.timeIntervalSinceNow)

With this code, you can see that commenting out line 4 or line 9 will not affect the output time.
Thanks for your help, what you say definitely makes sense. What I wanted to point out is that when commenting out line 8 (map[s] = s), the version with the string interpolation is faster than the one with String(format:), that's why I mentioned the dictionary insertion in the title.

What I wanted to point out is that when commenting out line 8 (map[s] = s), the version with the string interpolation is faster than the one with String(format:)

Thanks for clarifying. Now the title makes sense.

Do you need more explanation?
Swift String bridged from NSString has different internal representation than pure Swift String.
So, some operations to store in a pure Swift Dictionary may differ depending on the internal representation.
What still doesn't make sense is that the two variants inside the transform() function both use a Swift String, still one is faster to insert into the dictionary than the other.
Accepted Answer

What still doesn't make sense is that the two variants inside the transform() function both use a Swift String, still one is faster to insert into the dictionary than the other.

One uses NSString, and the other uses only pure Swift String.

I wrote:

(Please remember String(format: "%@/%@", s, s) considered as String(format: "%@/%@", s as NSString, s as NSString).)

But it was a little bit inaccurate.

Some of the methods in Swift String are just calling the equivalent methods of NSString.

String(format: "%@/%@", s, s) is equivalent to something like NSString(format: "%@/%@", s as NSString, s as NSString) as String.

I find it confusing that even when it's not prefixed with NS it's automatically bridged to NSString. Thanks for your help.

I find it confusing that even when it's not prefixed with NS it's automatically bridged to NSString.

Yes, truly confusing, but you need to know such implementation details when micro-performance is very important for you.
One more, this sort of implementation details may change in the future at any time without any notifications.

Anyway, hope my replies would help you improve your app.
Swift string interpolation produces string that takes very long to insert in a dictionary
 
 
Q