Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the FileManager.default.enumerator 3x faster with URL than String in Swift?

Tags:

macos

swift

I needed to traverse a large part (500k+ files) of the file tree, and switched from passing the FileManager.default.enumerator() a String to a URL. The traverse becomes 3x faster, and I am looking to understand why.

I'm testing on my Mac with an APFS formatted drive.

This is my test code to measure this in a Swift Playground:

import Cocoa

var startingTime: Date
var pathCount = 0
var urlCount = 0
    
let path = "/Users/tom/myfolder/"
let pathEnumerator = FileManager.default.enumerator(atPath: path)

let url = URL(fileURLWithPath: path)
let urlEnumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: nil)


print("== URL Enumerator ==")
startingTime = Date()
while let _ = urlEnumerator?.nextObject() as? URL {
    urlCount += 1
}
print("\n\(urlCount) files.")
print("\(startingTime.timeIntervalSinceNow * -1) seconds elapsed")

print("\n\n")

print("== Path/String Enumerator ==")
startingTime = Date()
while let _ = pathEnumerator?.nextObject() as? String {
    pathCount += 1
}
print("\n\(pathCount) files.")
print("\(startingTime.timeIntervalSinceNow * -1) seconds elapsed")

This is the output I get:

== URL Enumerator ==

541879 files.
40.580654978752136 seconds elapsed

== Path/String Enumerator ==

541879 files.
118.60869300365448 seconds elapsed

If I change the order (do the String version first), it makes no difference, so it doesn't appear to be a caching artefact.

like image 477
Tom Anthony Avatar asked Oct 23 '25 00:10

Tom Anthony


1 Answers

Thanks to the suggestion from @Willeke, I ran this from Xcode's "Time Profiler" tool, and looked at the call stack for each path. It has highlighted a difference in behaviour between the two methods.

The call stack for the URL approach:

URL call stack

The call stack for the String approach:

String (path) call stack

My best interpretation of this is that the older String API is a recursive crawl of tree, which comes with lots of performance overheads, whereas the newer URL API has 'knows' the file tree and can just traverse it iteratively.

@Martin R suggested I check this in a compiled app (which is where the screenshots are from). The difference there is still 2x, but they were both a lot faster (11s vs 22s) than in the Playground.

like image 196
Tom Anthony Avatar answered Oct 26 '25 00:10

Tom Anthony