Saya menerapkan algoritma di Swift Beta dan memperhatikan bahwa kinerjanya sangat buruk. Setelah menggali lebih dalam, saya menyadari bahwa salah satu hambatan adalah sesuatu yang sederhana seperti menyortir array. Bagian yang relevan ada di sini:
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
Di C ++, operasi yang sama mengambil 0,06s di komputer saya.
Dalam Python, dibutuhkan 0,6s (tidak ada trik, hanya y = diurutkan (x) untuk daftar bilangan bulat).
Di Swift dibutuhkan 6s jika saya mengompilasinya dengan perintah berikut:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
Dan dibutuhkan sebanyak 88-an jika saya mengompilasinya dengan perintah berikut:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Timing dalam Xcode dengan build "Release" vs. "Debug" serupa.
Apa yang salah di sini? Saya bisa memahami beberapa kerugian kinerja dibandingkan dengan C ++, tetapi bukan penurunan 10 kali lipat dibandingkan dengan Python murni.
Edit: cuaca memperhatikan bahwa perubahan -O3
untuk -Ofast
merek kode ini dijalankan hampir secepat C ++ versi! Namun, banyak -Ofast
mengubah semantik bahasa - dalam pengujian saya, itu menonaktifkan pemeriksaan untuk bilangan bulat bilangan bulat dan limpahan pengindeksan array . Misalnya, dengan -Ofast
kode Swift berikut ini berjalan secara diam-diam tanpa menabrak (dan mencetak beberapa sampah):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
Jadi -Ofast
bukan yang kita inginkan; Inti dari Swift adalah kita memiliki jaring pengaman di tempatnya. Tentu saja, jaring pengaman berdampak pada kinerja, tetapi seharusnya tidak membuat program 100 kali lebih lambat. Ingatlah bahwa Java sudah memeriksa batas array, dan dalam kasus-kasus tertentu, perlambatan adalah dengan faktor yang jauh lebih kecil dari 2. Dan di Dentang dan GCC kita punya -ftrapv
untuk memeriksa (menandatangani) bilangan bulat bilangan bulat, dan juga tidak terlalu lambat.
Karena itu pertanyaannya: bagaimana kita bisa mendapatkan kinerja yang wajar di Swift tanpa kehilangan jaring pengaman?
Sunting 2: Saya melakukan lebih banyak pembandingan, dengan loop yang sangat sederhana di sepanjang baris
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(Di sini operasi xor ada di sana hanya supaya saya dapat lebih mudah menemukan loop yang relevan dalam kode assembly. Saya mencoba untuk memilih operasi yang mudah dikenali tetapi juga "tidak berbahaya" dalam arti bahwa seharusnya tidak memerlukan pemeriksaan terkait untuk integer overflow.)
Sekali lagi, ada perbedaan besar dalam kinerja antara -O3
dan -Ofast
. Jadi saya melihat kode perakitan:
Dengan
-Ofast
saya mendapatkan cukup banyak apa yang saya harapkan. Bagian yang relevan adalah loop dengan 5 instruksi bahasa mesin.Dengan
-O3
saya mendapatkan sesuatu yang berada di luar imajinasi saya yang paling liar. Loop dalam mencakup 88 baris kode rakitan. Saya tidak mencoba memahami semua itu, tetapi bagian yang paling mencurigakan adalah 13 doa "callq _swift_retain" dan 13 doa lainnya "callq _swift_release". Yaitu, 26 panggilan subrutin di loop dalam !
Sunting 3: Dalam komentar, Ferruccio meminta tolok ukur yang adil dalam arti bahwa mereka tidak bergantung pada fungsi bawaan (mis. Sort). Saya pikir program berikut adalah contoh yang cukup baik:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
Tidak ada aritmatika, jadi kita tidak perlu khawatir tentang bilangan bulat bilangan bulat. Satu-satunya hal yang kami lakukan hanyalah banyak referensi array. Dan hasilnya ada di sini — Swift -O3 hilang dengan faktor hampir 500 dibandingkan dengan -Ofast:
- C ++ -O3: 0,05 dtk
- C ++ -O0: 0,4 dtk
- Java: 0,2 s
- Python dengan PyPy: 0,5 s
- Python: 12 dtk
- Cepat - Cepat: 0,05 dtk
- Swift -O3: 23 s
- Swift -O0: 443 dtk
(Jika Anda khawatir bahwa kompiler dapat mengoptimalkan loop sia-sia sepenuhnya, Anda dapat mengubahnya menjadi misalnya x[i] ^= x[j]
, dan menambahkan pernyataan cetak yang dihasilkan x[0]
. Ini tidak mengubah apa pun; waktunya akan sangat mirip.)
Dan ya, di sini implementasi Python adalah implementasi Python murni yang bodoh dengan daftar int dan bersarang untuk loop. Itu harus jauh lebih lambat daripada Swift yang tidak dioptimalkan. Sesuatu tampaknya sangat rusak dengan pengindeksan Swift dan array.
Sunting 4: Masalah-masalah ini (serta beberapa masalah kinerja lainnya) tampaknya telah diperbaiki di Xcode 6 beta 5.
Untuk menyortir, saya sekarang memiliki timing berikut:
- dentang ++ -O3: 0,06 dtk
- swiftc -Ofast: 0,1 s
- swiftc -O: 0,1 s
- swiftc: 4 dtk
Untuk loop bersarang:
- dentang ++ -O3: 0,06 dtk
- swiftc -Ofast: 0,3 s
- swiftc -O: 0,4 s
- swiftc: 540 s
Tampaknya tidak ada alasan lagi untuk menggunakan yang tidak aman -Ofast
(alias -Ounchecked
); plain -O
menghasilkan kode yang sama baiknya.
xcrun --sdk macosx swift -O3
. Lebih pendek.