Почему файловый менеджер.по умолчанию.перечислитель в 3 раза быстрее с URL-адресом, чем строка в Swift?

#swift #macos

Вопрос:

Мне нужно было пройти большую часть (более 500 тыс. файлов) дерева файлов и переключиться с передачи FileManager.default.enumerator() a String на a URL . Ход становится в 3 раза быстрее, и я пытаюсь понять, почему.

Я тестирую на своем Mac с диском в формате APFS.

Это мой тестовый код для измерения этого на игровой площадке Swift:

 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("nn")

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

Это результат, который я получаю:

 == URL Enumerator ==

541879 files.
40.580654978752136 seconds elapsed

== Path/String Enumerator ==

541879 files.
118.60869300365448 seconds elapsed
 

Если я изменю порядок ( String сначала сделаю версию), это не имеет значения, поэтому это не похоже на артефакт кэширования.

Комментарии:

1.Используемые API-интерфейсы URL являются более новыми, и вам рекомендуется их использовать, в то время как те, которые принимают String s, должны быть мягко устаревшими. Так что разница в производительности не слишком удивительна.

2. @Sweeper — Да, URL API-интерфейсы на основе более новые, но это все равно большая разница, особенно после того, как вы инициализировали путь, работа, которую должна выполнять функция, не должна слишком отличаться.

3. Вы пробовали «Профилировщик времени»инструментов?

4. Вы должны проводить тесты производительности в скомпилированном проекте, а не на игровой площадке.

5. @MartinR — Я только что попробовал это, и это все еще >в 2 раза быстрее, чем скомпилированный инструмент командной строки. Однако в целом они оба намного быстрее (11 секунд против 22), что было для меня неожиданностью. Это было полезно, так как я был одержим оптимизацией, когда она, возможно, вообще не нужна.

Ответ №1:

Благодаря предложению @Willeke я запустил это с помощью инструмента Xcode «Профилировщик времени» и просмотрел стек вызовов для каждого пути. Это высветило разницу в поведении между этими двумя методами.

Стек вызовов для URL подхода:

Стек вызовов URL-адресов

Стек вызовов для String подхода:

Стек вызовов строки (пути)

Моя лучшая интерпретация этого заключается в том, что более старый String API представляет собой рекурсивный обход дерева, который сопряжен с большими накладными расходами на производительность, в то время как более новый URL API «знает» дерево файлов и может просто итеративно его обходить.

@Martin R предложил мне проверить это в скомпилированном приложении (откуда взяты скриншоты). Разница все еще в 2 раза, но они оба были намного быстрее (11 против 22), чем на игровой площадке.