Tôi đang viết một số mã hiệu năng quan trọng trong Swift. Sau khi thực hiện tất cả các tối ưu hóa mà tôi có thể nghĩ đến, và lược tả ứng dụng trong Instruments, tôi đã nhận ra rằng phần lớn các chu kỳ CPU được dùng để thực hiện các hoạt động map()
và reduce()
trên mảng Floats. Vì vậy, chỉ để xem điều gì sẽ xảy ra, tôi đã thay thế tất cả các trường hợp map
và reduce
bằng các vòng lặp cũ for
. Và sự ngạc nhiên của tôi ... các vòng for
là nhiều, nhanh hơn nhiều!Hiệu suất Swift: map() và reduce() vs cho vòng lặp
Một chút bối rối bởi điều này, tôi quyết định thực hiện một số tiêu chuẩn thô. Trong một thử nghiệm, tôi đã map
trở lại một mảng của Floats sau khi thực hiện một số số học đơn giản như vậy:
// 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)
Và tương đương thực hiện for
loop:
var output = [Float]()
for element in array {
output.append(element * 5)
}
thời gian thực hiện trung bình cho map
: 20,1 giây. Thời gian thực hiện trung bình cho vòng lặp for
: 11,2 giây. Kết quả tương tự bằng cách sử dụng số nguyên thay vì nổi.
Tôi đã tạo một điểm chuẩn tương tự để kiểm tra hiệu suất của Swift reduce
. Lần này, các vòng reduce
và for
đạt được hiệu suất gần như giống nhau khi tổng hợp các phần tử của một mảng lớn. Nhưng khi tôi vòng lặp kiểm tra 100.000 lần như thế này:
// 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)
vs:
for _ in 0..<100_000 {
var sum: Float = 0
for element in array {
sum += element
}
}
Phương pháp reduce
mất 29 giây trong khi for
loop mất (rõ ràng) 0,000003 giây.
Đương nhiên tôi đã sẵn sàng bỏ qua thử nghiệm cuối cùng là kết quả tối ưu hóa trình biên dịch, nhưng tôi nghĩ nó có thể cung cấp một số thông tin chi tiết về cách trình biên dịch tối ưu hóa khác nhau cho các vòng lặp so với các phương thức mảng tích hợp của Swift. Lưu ý rằng tất cả các thử nghiệm được thực hiện với tối ưu hóa -Os trên một i7 MacBook Pro 2,5 GHz. Kết quả khác nhau tùy thuộc vào kích thước mảng và số lần lặp lại, nhưng vòng lặp for
luôn hoạt động tốt hơn các phương pháp khác ít nhất 1.5x, đôi khi lên tới 10x.
Tôi hơi bối rối về hiệu suất của Swift tại đây. Các phương thức Array tích hợp có nhanh hơn phương pháp ngây thơ để thực hiện các hoạt động như vậy không? Có lẽ ai đó có kiến thức cấp thấp hơn tôi có thể làm sáng tỏ tình hình.
Rất có thể, trình biên dịch nhận ra rằng trong ví dụ cuối cùng của bạn, kết quả của tổng kết không được sử dụng và loại bỏ toàn bộ vòng lặp. In tổng sau khi vòng lặp sẽ tạo sự khác biệt. –
Ý tưởng hay - điều đó chắc chắn làm chậm nó xuống. Mặc dù thành thật mà nói, trong kinh nghiệm của tôi gọi print() mà nhiều lần là cực kỳ chậm anyway, vì vậy thật khó để nói những gì khác biệt nó làm cho. Đó là một ví dụ điển hình về sự khác biệt tối ưu giữa hai phương thức - có vẻ như nó cũng nên đưa ra kết luận tương tự về vòng lặp reduce(). – hundley
Có lẽ bài viết này có thể cung cấp một số thông tin chi tiết về sự khác biệt về hiệu suất giữa các vòng lặp và giảm: http://airspeedvelocity.net/2015/08/03/arrays-linked-lists-and-performance/ – JDS