Производительность сортировки массивов в Swift Beta: почему так медленно и как ускорить?
Ключевая проблема: отладочная сборка и отсутствие оптимизаций
Вы столкнулись с классической ловушкой для новичков в Swift — разницей между Debug и Release сборками. В Xcode 6 Beta (да и в современных версиях) по умолчанию для Debug-схемы установлен уровень оптимизации -Onone, который вообще не оптимизирует код. Это сделано для ускорения компиляции и удобства отладки, но производительность страдает катастрофически.
Когда вы сортируете массив из 100 000 элементов через замыкание { $0 > $1 }, компилятор в Debug-режиме не применяет:
- Инлайнинг замыканий (встраивание кода)
- Специализацию обобщённого кода (generic specialization)
- Оптимизацию по скорости (например, удаление лишних проверок границ)
В результате каждый вызов сравнения проходит через полную динамическую диспетчеризацию, что в 50-100 раз медленнее, чем в Release.
Решение: измените схему сборки
Вот пошаговая инструкция, как исправить ситуацию:
- Выберите в Xcode Product → Scheme → Edit Scheme (или нажмите
Cmd + <) - В левой панели выберите Run
- Переключите Build Configuration с Debug на Release
- Закройте окно и запустите проект заново
После этого та же самая сортировка будет выполняться за 0.05–0.1 секунды — наравне с Objective-C.
Почему это работает?
В Release-режиме с оптимизацией -O компилятор Swift делает следующее:
- Специализирует обобщённый алгоритм — для типа
Intгенерируется отдельная версияsort(), работающая напрямую с числами, без боксинга - Инлайнит замыкание — вместо вызова функции сравнения код
$0 > $1встраивается прямо в алгоритм сортировки - Убирает проверки границ — для массивов, созданных в локальной области видимости, Swift может доказать, что индексы всегда корректны
Дополнительные советы для максимальной производительности
// 1. Используйте оператор > напрямую (если Comparable)
numbers.sort(by: >) // быстрее, чем замыкание
// 2. Для структур используйте сортировку на месте
array.sort() // изменяет оригинал, без копирования
// 3. Если массив большой и не нужно менять оригинал:
let sorted = array.sorted() // создаёт копию
// 4. Для кастомных объектов — реализуйте Comparable
struct Person: Comparable {
let name: String
let age: Int
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.age < rhs.age
}
}
Важно: Всегда тестируйте производительность на Release-сборке. Debug предназначен только для разработки и отладки — его скорость не показательна.
<<Альтернативный подход: ручная сортировка и профилирование
Если по каким-то причинам вы не можете переключиться на Release (например, нужно отлаживать алгоритм), попробуйте:
// Быстрая сортировка вручную
func quickSort<T: Comparable>(_ arr: inout [T], low: Int, high: Int) {
if low < high {
let pivot = partition(&arr, low: low, high: high)
quickSort(&arr, low: low, high: pivot - 1)
quickSort(&arr, low: pivot + 1, high: high)
}
}
// Использование
var numbers = // ... большой массив
quickSort(&numbers, low: 0, high: numbers.count - 1)
Это даст прирост в Debug-режиме, потому что вы избегаете обобщённого алгоритма стандартной библиотеки, который в Debug работает через динамическую диспетчеризацию.
Также рекомендую использовать инструмент Instruments (Time Profiler) для профилирования. Запустите на Release-сборке и посмотрите, какие функции потребляют больше всего времени. Часто оказывается, что "медленная сортировка" на самом деле — это медленные компараторы или лишние копирования данных.
И последнее: если вам нужно отсортировать массив один раз и потом только читать, используйте sorted() вместо sort() — разница в производительности минимальна, но код становится безопаснее (не мутирует оригинал).