“loop unrolling” в Swift или как разогнать ваш код с помощью одного небольшого фокуса.
Современные устройства настолько мощные, что из-за этого некоторые из нас стали упускать из виду важность производительности и оптимизации. И действительно, зачем вообще беспокоиться об оптимальном использовании ресурсов, когда в наших Mac или iPad зашит такой высокопроизводительный монстр, как M2 SoC? Однако принятие такого рода мышления является пагубным. Очень важно время от времени пересматривать основы и принимать на вооружение дельные советы по оптимизации кода. Они могут обогатить наши знания и улучшить наши навыки разработки программных продуктов, даже если они не всегда могут пригодиться на практике.
Сегодня мы с вами рассмотрим функцию, которой каждый из нас пользуется чуть ли не ежедневно: метод filter.
Пишем собственный метод filter
Фильтрация массива — типовая задача, поэтому было бы интересно попробовать ее оптимизировать.
Давайте возьмем массив имен и попробуем отфильтровать конкретное имя:
func filterName(name: String,
fromArray collection: [String]) -> [String] {
var result: [String] = []
let indices = collection.indices
var currentIndex = indices.lowerBound
while currentIndex < indices.upperBound {
let tempName = collection[currentIndex]
if tempName == name {
result.append(tempName)
}
currentIndex = indices.index(after: currentIndex)
}
return result
}
Код для выполнения этой задачи довольно прост. Мы перебираем массив от нижней границы к верхней, обращаемся к каждому его элементу и производим сравнение. Наконец, мы возвращаем все отфильтрованные элементы.
Я провел тест на 500 000 элементов, и время выполнения составило 0.056 секунды. Неплохо! Теперь давайте рассмотрим некоторые потенциальные оптимизации для дальнейшего повышения производительности.
Использование forEach
Кое-кто из вас уже мог заметить, что предыдущая функция выглядит немного громоздкой. В конце концов, зачем возиться с созданием нижних и верхних границ, когда мы можем просто использовать для массива метод forEach? Давайте посмотрим на пример того же кода, но реализованный через цикл forEach:
func filterName(name: String, fromArray collection: [String]) -> [String] {
var result: [String] = []
for tempName in collection {
if tempName == name {
result.append(tempName)
}
}
return result
}
Теперь, когда я запущу этот код на тех же самых 500 000 элементах, результат будет получен куда быстрее. Время выполнения составило лишь 0.031 секунды. Это значительное улучшение — на целых 42%!
Но за счет чего это происходит? Что ж, под капотом сама функция forEach все еще выполняет итерации. Однако стоит отметить, что forEach — это встроенный метод, который может быть оптимизирован средой выполнения или компилятором, что может быть причиной ощутимого повышения производительности по сравнению с циклами, реализованными вручную.
Итерирование в Swift часто более оптимизировано, чем доступ к элементам по отдельности. Последовательная итерация позволяет компилятору реализовать эффективный доступ к элементам и даже выполнять предварительную выборку элементов, чтобы свести все возможные задержки к минимуму. Однако можем ли мы расширить границы возможной оптимизации еще дальше? Давайте попробуем найти еще пару способов улучшить наш код