Avatar ·

Производительность сортировки массивов в Swift Beta: почему так медленно и как ускорить?

Всем привет! Я только начал изучать Swift и столкнулся с очень странной проблемой при сортировке массивов в Xcode 6 Beta. У меня есть простой массив целых чисел, и я хочу отсортировать его по убыванию. Написал такой код: ```swift var numbers = [9, 3, 5, 1, 8, 2, 7, 4, 6] numbers.sort { $0 > $1 } ``` Вроде бы всё логично. Но когда я запускаю этот код на большом массиве (скажем, 100 000 элементов), сортировка занимает целую вечность — около 4-5 секунд! А если я делаю то же самое на Objective-C с `sortedArrayUsingComparator:`, то всё работает мгновенно (менее 0.1 секунды). Я попробовал разные варианты: 1. `numbers.sort(>)` — та же медлительность. 2. `numbers.sorted(by: >)` — возвращает новый массив, но тоже тормозит. 3. Даже простой `numbers.sort()` без замыкания — всё равно медленно. В чём может быть дело? Это баг бета-версии или я что-то делаю не так? Может быть, нужно включать какие-то оптимизации компилятора? Очень разочарован производительностью, так как Swift позиционируется как быстрый язык. P.S. Использую Debug-схему сборки, может, это имеет значение? <<>>

Ключевая проблема: отладочная сборка и отсутствие оптимизаций

Вы столкнулись с классической ловушкой для новичков в Swift — разницей между Debug и Release сборками. В Xcode 6 Beta (да и в современных версиях) по умолчанию для Debug-схемы установлен уровень оптимизации -Onone, который вообще не оптимизирует код. Это сделано для ускорения компиляции и удобства отладки, но производительность страдает катастрофически.

Когда вы сортируете массив из 100 000 элементов через замыкание { $0 > $1 }, компилятор в Debug-режиме не применяет:

  • Инлайнинг замыканий (встраивание кода)
  • Специализацию обобщённого кода (generic specialization)
  • Оптимизацию по скорости (например, удаление лишних проверок границ)

В результате каждый вызов сравнения проходит через полную динамическую диспетчеризацию, что в 50-100 раз медленнее, чем в Release.

Решение: измените схему сборки

Вот пошаговая инструкция, как исправить ситуацию:

  1. Выберите в Xcode Product → Scheme → Edit Scheme (или нажмите Cmd + <)
  2. В левой панели выберите Run
  3. Переключите Build Configuration с Debug на Release
  4. Закройте окно и запустите проект заново

После этого та же самая сортировка будет выполняться за 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() — разница в производительности минимальна, но код становится безопаснее (не мутирует оригинал).