Я пишу критически важный код в Swift. После реализации всех оптимизаций, которые я мог придумать, и профилирования приложения в "Инструменты", я понял, что большая часть циклов ЦП расходуется на выполнение операций map()
и reduce()
на массивах Floats. Итак, чтобы увидеть, что произойдет, я заменил все экземпляры map
и reduce
на старые старомодные циклы for
. И к моему изумлению... петли for
были намного, намного быстрее!
Немного озадаченный этим, я решил выполнить некоторые приблизительные тесты. В одном тесте у меня map
возвращался массив Floats после выполнения простой арифметики, например:
// Populate array with 1,000,000,000 random numbers
var array = [Float](count: 1_000_000_000, repeatedValue: 0)
for i in 0..<array.count {
array[i] = Float(random())
}
let start = NSDate()
// Construct a new array, with each element from the original multiplied by 5
let output = array.map({ (element) -> Float in
return element * 5
})
// Log the elapsed time
let elapsed = NSDate().timeIntervalSinceDate(start)
print(elapsed)
И эквивалентная реализация цикла for
:
var output = [Float]()
for element in array {
output.append(element * 5)
}
Среднее время выполнения для map
: 20,1 секунды. Среднее время выполнения для цикла for
: 11,2 секунды. Результаты были схожи с использованием целых чисел вместо Floats.
Я создал аналогичный тест, чтобы проверить производительность Swift reduce
. На этот раз петли reduce
и for
достигли почти такой же производительности при суммировании элементов одного большого массива. Но когда я проведу тест 100 000 раз так:
// Populate array with 1,000,000 random numbers
var array = [Float](count: 1_000_000, repeatedValue: 0)
for i in 0..<array.count {
array[i] = Float(random())
}
let start = NSDate()
// Perform operation 100,000 times
for _ in 0..<100_000 {
let sum = array.reduce(0, combine: {$0 + $1})
}
// Log the elapsed time
let elapsed = NSDate().timeIntervalSinceDate(start)
print(elapsed)
против
for _ in 0..<100_000 {
var sum: Float = 0
for element in array {
sum += element
}
}
Метод reduce
занимает 29 секунд, в то время как цикл for
занимает (по-видимому) 0,000003 секунды.
Естественно, я готов проигнорировать этот последний тест в результате оптимизации компилятора, но я думаю, что он может дать некоторое представление о том, как компилятор по-разному оптимизируется для встроенных методов массивов Loops vs Swift. Обратите внимание, что все тесты проводились с оптимизацией -Os на MacBook Pro с тактовой частотой 2,5 ГГц. Результаты варьировались в зависимости от размера массива и количества итераций, но циклы for
всегда превосходили другие методы не менее чем на 1,5x, иногда до 10x.
Я немного недоумеваю о производительности Swift здесь. Не должны ли встроенные методы Array быть быстрее, чем наивный подход для выполнения таких операций? Может быть, кто-то с более низким уровнем знаний, чем я могу пролить свет на ситуацию.