Tôi đã thực hiện một thuật toán trong Swift Beta và nhận thấy rằng hiệu suất rất kém. Sau khi đào sâu hơn, tôi nhận ra rằng một trong những điểm nghẽn là một thứ đơn giản như sắp xếp các mảng. Phần có liên quan ở đây:
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
Trong C ++, một thao tác tương tự mất 0,06 giây trên máy tính của tôi.
Trong Python, phải mất 0,6 giây (không có thủ thuật, chỉ cần y = sort (x) cho danh sách các số nguyên).
Trong Swift, tôi mất 6 giây nếu tôi biên dịch nó bằng lệnh sau:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
Và phải mất tới 88 giây nếu tôi biên dịch nó bằng lệnh sau:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Thời gian trong Xcode với các bản dựng "Phát hành" so với "Gỡ lỗi" là tương tự nhau.
Có chuyện gì ở đây vậy? Tôi có thể hiểu một số mất hiệu năng so với C ++, nhưng không chậm hơn 10 lần so với Python thuần túy.
Chỉnh sửa: thời tiết nhận thấy rằng việc thay đổi -O3
để -Ofast
làm cho mã này chạy gần như nhanh như phiên bản C ++! Tuy nhiên, -Ofast
thay đổi ngữ nghĩa của ngôn ngữ rất nhiều - trong thử nghiệm của tôi, nó đã vô hiệu hóa các kiểm tra cho tràn số nguyên và tràn chỉ mục mảng . Ví dụ: với -Ofast
mã Swift sau đây chạy âm thầm mà không gặp sự cố (và in ra một số rác):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
Vì vậy, -Ofast
không phải là những gì chúng ta muốn; toàn bộ quan điểm của Swift là chúng tôi có lưới an toàn. Tất nhiên, lưới an toàn có một số tác động đến hiệu suất, nhưng chúng không nên làm cho chương trình chậm hơn 100 lần. Hãy nhớ rằng Java đã kiểm tra giới hạn mảng và trong các trường hợp điển hình, sự chậm lại là do hệ số nhỏ hơn 2. Và trong Clang và GCC, chúng ta đã có -ftrapv
để kiểm tra số nguyên (đã ký) tràn ra và nó cũng không chậm.
Do đó, câu hỏi: làm thế nào chúng ta có thể có được hiệu suất hợp lý trong Swift mà không mất mạng lưới an toàn?
Chỉnh sửa 2: Tôi đã thực hiện thêm một số điểm chuẩn, với các vòng lặp rất đơn giản dọc theo dòng
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(Ở đây có thao tác xor chỉ để tôi có thể dễ dàng tìm thấy vòng lặp có liên quan hơn trong mã lắp ráp. Tôi đã cố gắng chọn một thao tác dễ phát hiện nhưng cũng "vô hại" theo nghĩa là nó không yêu cầu bất kỳ kiểm tra nào liên quan để tràn số nguyên.)
Một lần nữa, có một sự khác biệt rất lớn trong hiệu suất giữa -O3
và -Ofast
. Vì vậy, tôi đã xem xét mã lắp ráp:
Với
-Ofast
tôi nhận được khá nhiều những gì tôi mong đợi. Phần liên quan là một vòng lặp với 5 hướng dẫn ngôn ngữ máy.Với
-O3
tôi nhận được một cái gì đó ngoài sức tưởng tượng điên cuồng nhất của tôi. Vòng lặp bên trong kéo dài 88 dòng mã lắp ráp. Tôi đã không cố gắng để hiểu tất cả về nó, nhưng phần đáng ngờ nhất là 13 cách gọi "callq _swift_retain" và 13 cách gọi khác của "callq _swift_release". Đó là, 26 cuộc gọi chương trình con trong vòng lặp bên trong !
Chỉnh sửa 3: Trong các bình luận, Ferruccio đã yêu cầu các điểm chuẩn công bằng theo nghĩa là chúng không dựa vào các chức năng tích hợp (ví dụ: sắp xếp). Tôi nghĩ rằng chương trình sau đây là một ví dụ khá hay:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
Không có số học, vì vậy chúng tôi không cần phải lo lắng về việc tràn số nguyên. Điều duy nhất mà chúng tôi làm chỉ là rất nhiều tài liệu tham khảo mảng. Và kết quả là ở đây, Swift Swift -O3 thua với hệ số gần 500 so với -Ofast:
- C ++ -O3: 0,05 giây
- C ++ -O0: 0,4 giây
- Java: 0,2 giây
- Python với PyPy: 0,5 s
- Con trăn: 12 giây
- Swift -Fast: 0,05 giây
- Swift -O3: 23 giây
- Swift -O0: 443 giây
(Nếu bạn lo ngại rằng trình biên dịch có thể tối ưu hóa hoàn toàn các vòng lặp vô nghĩa, bạn có thể thay đổi nó thành ví dụ x[i] ^= x[j]
và thêm một câu lệnh in xuất ra x[0]
. Điều này không thay đổi bất cứ điều gì; thời gian sẽ rất giống nhau.)
Và vâng, ở đây, việc triển khai Python là một triển khai Python thuần túy ngu ngốc với một danh sách các số nguyên và được lồng cho các vòng lặp. Nó sẽ được nhiều chậm hơn so với unoptimized Swift. Một cái gì đó dường như bị phá vỡ nghiêm trọng với Swift và lập chỉ mục mảng.
Chỉnh sửa 4: Những sự cố này (cũng như một số vấn đề về hiệu suất khác) dường như đã được khắc phục trong Xcode 6 beta 5.
Để sắp xếp, bây giờ tôi có các thời gian sau:
- kêu vang ++ -O3: 0,06 giây
- swiftc -Fast: 0,1 s
- swiftc -O: 0,1 s
- swiftc: 4 giây
Đối với các vòng lặp lồng nhau:
- kêu vang ++ -O3: 0,06 giây
- swiftc -Fast: 0,3 giây
- swiftc -O: 0,4 giây
- swiftc: 540 giây
Dường như không còn lý do nào nữa để sử dụng không an toàn -Ofast
(aka -Ounchecked
); đồng bằng -O
sản xuất mã tốt như nhau.
xcrun --sdk macosx swift -O3
. Nó ngắn hơn.