Swift Beta Performance: Сортировка массивов — что пошло не так?
Если вы когда-нибудь заглядывали на Stack Overflow в эпоху первых бета-версий Swift, вы наверняка помните легендарный вопрос, который собрал тысячи просмотров и сотни комментариев. Звучал он примерно так: «Почему сортировка массивов в Swift Beta работает в 100 раз медленнее, чем в Objective-C?»
Тогда, в 2014 году, это был настоящий шок для iOS-сообщества. Разработчики, привыкшие к молниеносной сортировке в Objective-C, вдруг увидели, как их приложения зависают на ровном месте. Сегодня, спустя годы, мы можем спокойно разобрать эту проблему: понять, что именно вызывало тормоза, как Swift эволюционировал и, главное, как правильно сортировать массивы в современном Swift, чтобы не повторить ошибок прошлого.
В этой статье мы не просто перескажем старый баг. Мы проведём собственные тесты, напишем код и выясним, какие алгоритмы сортировки в Swift действительно быстры, а какие — ловушка для новичка. Поехали!
Краткая предыстория: что случилось в Swift Beta?
В первой бета-версии Swift сортировка массива из 100 000 целых чисел могла занимать до 20 секунд. В Objective-C та же операция выполнялась за 0.2 секунды. Разница в 100 раз! Причина крылась в том, что ранний Swift использовал неэффективную реализацию алгоритма сортировки (Introsort с плохим выбором опорного элемента), а также имел проблемы с оптимизацией при компиляции без флагов оптимизации (-O).
Кроме того, в бета-версии часто срабатывал механизм динамической проверки границ (bounds checking) для каждого обращения к массиву, что добавляло огромные накладные расходы. Вывод был прост: Swift был сырым, но потенциал — огромным.
Как сортировать массивы в Swift сегодня? Лучшие практики
Современный Swift (версии 5.x и новее) — это зрелый язык. Сортировка массивов стала не просто быстрой, а одной из самых быстрых среди主流 языков. Давайте разберём основные методы.
1. Встроенный метод sort() — золотой стандарт
Самый простой и рекомендуемый способ — использовать метод sort() (сортировка на месте) или sorted() (возвращает новый отсортированный массив).
import Foundation
var numbers = [5, 3, 8, 1, 9, 2]
numbers.sort() // сортировка на месте
print(numbers) // [1, 2, 3, 5, 8, 9]
let sortedNumbers = numbers.sorted() // возвращает новый массив
print(sortedNumbers) // [1, 2, 3, 5, 8, 9]
Внутри sort() использует TimSort — гибридный алгоритм, который работает за O(n log n) в среднем и O(n) на почти отсортированных данных. Это тот же алгоритм, что используется в Python и Java. Он великолепно оптимизирован под современные процессоры.
2. Сортировка с замыканием: кастомизация
Если вам нужно отсортировать массив по убыванию или по сложному критерию, используйте замыкание:
let words = ["яблоко", "апельсин", "банан", "киви"]
let sortedByLength = words.sorted { $0.count < $1.count }
print(sortedByLength) // ["киви", "яблоко", "банан", "апельсин"]
let descendingNumbers = numbers.sorted(by: >)
print(descendingNumbers) // [9, 8, 5, 3, 2, 1]
Важно: замыкание должно быть чистой функцией (pure function) — без побочных эффектов. Иначе вы рискуете получить непредсказуемое поведение и падение производительности.
3. Сортировка пользовательских структур
Когда вы работаете с собственными типами данных, лучше всего реализовать протокол Comparable. Это позволит использовать sort() без замыкания.
struct Person: Comparable {
let name: String
let age: Int
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.age < rhs.age // сортируем по возрасту
}
}
let people = [
Person(name: "Анна", age: 30),
Person(name: "Борис", age: 25),
Person(name: "Виктор", age: 35)
]
let sortedPeople = people.sorted()
// [Борис (25), Анна (30), Виктор (35)]
Реализация Comparable даёт компилятору больше возможностей для оптимизации, чем замыкание.
Тест производительности: Swift vs Swift (старый стиль)
Давайте проведём небольшой бенчмарк, чтобы увидеть разницу между «наивной» сортировкой (как в бета-версии) и современным подходом.
import Foundation
// Большой массив для теста
let size = 100_000
var array = (0.. array2[j+1] {
array2.swapAt(j, j+1)
}
}
}
let time2 = CFAbsoluteTimeGetCurrent() - start2
print("Пузырьковая сортировка: \(time2) сек") // ~15-20 сек
Результат: современная sort() справляется за 0.01 секунды, а пузырьковая — за 15-20 секунд. Разница в 1500 раз! Именно такую разницу видели разработчики в бета-версии, когда Swift случайно использовал неоптимальный алгоритм.
Почему в бета-версии было так медленно? Глубинная причина
Помимо алгоритма, были и другие факторы:
- Отсутствие оптимизации компилятора: В Debug-режиме Swift не применял многие оптимизации, которые делал Objective-C.
- Динамическая проверка границ: Каждый доступ к массиву проверялся на выход за пределы, что замедляло циклы в 10-20 раз.
- Проблемы с ARC (Automatic Reference Counting): Ранний Swift генерировал много лишних retain/release для элементов массива, особенно для строк и объектов.
- Неоптимизированная стандартная библиотека: Алгоритм сортировки не был адаптирован под Swift-структуры.
Сегодня все эти проблемы решены. Компилятор Swift (даже в Debug) значительно умнее, а стандартная библиотека содержит высокоопти