Parse Int from Substring slower than String

I want to parse numbers from a string that contains multiple numbers separated by a space. After splitting the string, however, I found that rather than converting the split Substring to Int directly, converting it to String at first and then converting to Int is much faster.

I did a benchmark using Google's swift-benchmark:

// inputline is like "86 47 85 19 37 38 73"
let inputline: String = (1...10000)
    .map{ _ in String(Int.random(in: 0...1000)) }
    .joined(separator: " ")

let substrings = inputline.split(separator: " ")

benchmark("Substring -> Int") {
    let _ = substrings.map { Int($0)! }
}

benchmark("Substring -> String -> Int") {
    let _ = substrings.map { Int(String($0))! }
}
name                       time           std        iterations
---------------------------------------------------------------
Substring -> Int           6714750.000 ns ±  11.37 %        188
Substring -> String -> Int  450150.000 ns ±  11.97 %       2755

Theoretically, the Int initializer should just read the (sub)string's memory and won't modify it in any way. So there shouldn't be any additional memory tasks. I checked the source code of IntegerParser.swift but couldn't find any information.

Couldn't understand why it happens. Any thoughts would be appreciated.

1 Like

I'm going to guess that you're measuring performance of a debug build. Here's what I get running your code with a release build:

name                       time          std        iterations
--------------------------------------------------------------
Substring -> Int           278842.000 ns ±   9.61 %       4875
Substring -> String -> Int 462670.500 ns ±   5.66 %       2976

I ran it on Xcode 12.5.1 and CLI (swift 5.4.2), both in release mode, and got the same result indicating the latter one is faster. Strange.

Ah, you're using Swift 5.4! The code you're reading in IntegerParsing.swift was rewritten (by me) in March, so you'll need macOS 12/Swift 5.5 to see the results I posted. The previous implementation was indeed over 10 times slower for Substring, one of the reasons I rewrote it.

My main preoccupation during the rewrite was making sure code size didn't regress; improving the performance of Substring was so obviously happening that I neglected to include a benchmark. However, it'd probably be wise to add one to the standard library repository so that future revisions don't cause an unintended regression--that's a good starter issue I think. I'm happy to point you in the right direction if you're interested; if not, perhaps you could file a starter bug at bugs.swift.org?

4 Likes

Aha, yes! I ran it on Xcode13-beta and got the correct result, much faster. Great job!

Pretty sure I have the interest to contribute to the standard library, but not sure what you're suggesting to me. Are you saying post an issue to the repository that suggests adding a benchmark? Or even further, to write some benchmark code then PR…?

However much you're comfortable with! Issues are posted at bugs.swift.org, not on GitHub, but if you have a little experience with git (or even if you don't), creating a PR to add a benchmark is a pretty nice way to dip your toes into contributing to the standard library--the biggest barrier will be setting up the project to build locally, and the instructions are now pretty good.

Okay. I'm familiar with git but have no experience about contributing to Swift (nor even compiling it). Took a look at Contributing and thought there'll be a long way to go but I'm glad to try it.

Thanks!

4 Likes