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.
sumber
xcrun --sdk macosx swift -O3
. Lebih pendek.Jawaban:
tl; dr Swift 1.0 sekarang lebih cepat dari C oleh tolok ukur ini menggunakan tingkat optimisasi rilis default [-O].
Berikut adalah quicksort di tempat di Swift Beta:
Dan hal yang sama dalam C:
Keduanya bekerja:
Keduanya dipanggil dalam program yang sama seperti yang tertulis.
Ini mengonversi waktu absolut ke detik:
Berikut ini adalah ringkasan tingkat optimisasi kompiler:
Waktu dalam detik dengan [-Onone] untuk n = 10_000 :
Ini adalah jenis bawaan Swift () untuk n = 10_000 :
Ini [-O] untuk n = 10_000 :
Seperti yang Anda lihat, kinerja Swift meningkat dengan faktor 20.
Seperti jawaban per mweathers , pengaturan [-Ofast] membuat perbedaan nyata, menghasilkan waktu ini untuk n = 10_000 :
Dan untuk n = 1_000_000 :
Sebagai perbandingan, ini dengan [-Satu] untuk n = 1_000_000 :
Jadi Swift tanpa optimasi hampir 1000x lebih lambat daripada C dalam benchmark ini, pada tahap ini dalam pengembangannya. Di sisi lain dengan kedua kompiler diatur ke [-Ofast] Swift benar-benar tampil setidaknya juga jika tidak sedikit lebih baik daripada C.
Telah ditunjukkan bahwa [-Ofast] mengubah semantik bahasa, membuatnya berpotensi tidak aman. Inilah yang Apple nyatakan dalam catatan rilis Xcode 5.0:
Mereka semua menganjurkannya. Entah itu bijaksana atau tidak, saya tidak bisa mengatakannya, tetapi dari apa yang saya bisa katakan, rasanya cukup masuk akal untuk menggunakan [-Ofast] dalam rilis jika Anda tidak melakukan aritmatika floating point presisi tinggi dan Anda yakin tidak memiliki bilangan bulat atau array overflow dimungkinkan dalam program Anda. Jika Anda memang membutuhkan pemeriksaan kinerja tinggi dan melimpah / aritmatika yang tepat, maka pilih bahasa lain untuk saat ini.
PEMBARUAN BETA 3:
n = 10_000 dengan [-O] :
Swift secara umum sedikit lebih cepat dan sepertinya jenis bawaan Swift telah berubah cukup signifikan.
PEMBARUAN AKHIR:
[-Onone] :
[-O] :
[-Dilihat] :
sumber
TL; DR : Ya, satu-satunya implementasi bahasa Swift lambat, saat ini . Jika Anda memerlukan kode yang cepat, numerik (dan jenis kode lainnya, mungkin), cukup gunakan yang lain. Di masa depan, Anda harus mengevaluasi kembali pilihan Anda. Mungkin cukup baik untuk sebagian besar kode aplikasi yang ditulis pada level yang lebih tinggi.
Dari apa yang saya lihat di SIL dan LLVM IR, sepertinya mereka membutuhkan banyak optimasi untuk menghapus retain dan rilis, yang mungkin diimplementasikan di Dentang (untuk Objective-C), tetapi mereka belum porting mereka. Itulah teori yang saya ajukan (untuk saat ini ... saya masih perlu memastikan bahwa Clang melakukan sesuatu tentang hal itu), karena seorang profiler yang menjalankan uji kasus terakhir dari pertanyaan ini menghasilkan hasil yang “cantik” ini:
Seperti yang dikatakan oleh banyak orang lain,
-Ofast
sama sekali tidak aman dan mengubah semantik bahasa. Bagi saya, itu pada tahap "Jika Anda akan menggunakannya, gunakan saja bahasa lain". Saya akan mengevaluasi kembali pilihan itu nanti, jika itu berubah.-O3
memberi kita banyakswift_retain
danswift_release
panggilan itu, jujur, tidak terlihat seperti mereka harus ada untuk contoh ini. Pengoptimal seharusnya menghilangkan (sebagian besar) dari mereka AFAICT, karena ia mengetahui sebagian besar informasi tentang array, dan tahu bahwa ia memiliki (setidaknya) referensi kuat untuk itu.Seharusnya tidak memancarkan lebih banyak mempertahankan ketika itu bahkan tidak memanggil fungsi yang mungkin melepaskan objek. Saya tidak berpikir konstruktor array dapat mengembalikan array yang lebih kecil dari yang diminta, yang berarti bahwa banyak pemeriksaan yang dipancarkan tidak berguna. Ia juga tahu bahwa integer tidak akan pernah di atas 10k, jadi pemeriksaan overflow dapat dioptimalkan (bukan karena
-Ofast
keanehan, tetapi karena semantik bahasa (tidak ada yang mengubah var itu dan tidak dapat mengaksesnya, dan menambahkan hingga 10k aman untuk tipenyaInt
).Kompiler mungkin tidak dapat membuka kotak array atau elemen array, karena mereka diteruskan ke
sort()
, yang merupakan fungsi eksternal dan harus mendapatkan argumen yang diharapkan. Ini akan membuat kita harus menggunakanInt
nilai secara tidak langsung, yang akan membuatnya sedikit lebih lambat. Ini bisa berubah jikasort()
fungsi generik (tidak dengan cara multi-metode) tersedia untuk kompiler dan mendapat inline.Ini adalah bahasa yang sangat baru (secara publik), dan sedang melalui apa yang saya asumsikan banyak perubahan, karena ada orang (banyak) yang terlibat dengan bahasa Swift meminta umpan balik dan mereka semua mengatakan bahasa itu belum selesai dan akan perubahan.
Kode yang digunakan:
PS: Saya bukan ahli Objective-C atau semua fasilitas dari Cocoa , Objective-C, atau runtime Swift. Saya mungkin juga mengasumsikan beberapa hal yang tidak saya tulis.
sumber
-Ofast
"sama sekali tidak aman"? Dengan asumsi Anda tahu cara menguji kode Anda dan mengesampingkan luapan.-Ofast
juga mengubah semantik bahasa, tetapi saya tidak dapat mendanai dokumen apa pun untuk itu. Bagaimana Anda bisa yakin bahwa Anda tahu apa yang dilakukannya?Int[]
' karena itu tergantung pada level yang bisa di-compiler oleh kompiler dan seharusnya bisa inline loop ketat dengan / setelah beberapa panduan.Saya memutuskan untuk melihatnya untuk bersenang-senang, dan inilah waktu yang saya dapatkan:
Cepat
Hasil:
Swift 1.1
Cepat 1.2
Swift 2.0
Tampaknya kinerja yang sama jika saya kompilasi dengan
-Ounchecked
.Swift 3.0
Tampaknya ada regresi kinerja dari Swift 2.0 ke Swift 3.0, dan saya juga melihat perbedaan antara
-O
dan-Ounchecked
untuk pertama kalinya.Cepat 4.0
Swift 4 meningkatkan kinerja lagi, sambil menjaga jarak antara
-O
dan-Ounchecked
.-O -whole-module-optimization
tampaknya tidak membuat perbedaan.C ++
Hasil:
Apple Clang 6.0
Apple Clang 6.1.0
Apple Clang 7.0.0
Apple Clang 8.0.0
Apple Clang 9.0.0
Putusan
Pada saat penulisan ini, jenis Swift cepat, tetapi belum secepat jenis C ++ ketika dikompilasi
-O
, dengan kompiler & pustaka di atas. Dengan-Ounchecked
, tampaknya secepat C ++ di Swift 4.0.2 dan Apple LLVM 9.0.0.sumber
Dari
The Swift Programming Language
:The
sort
fungsi memiliki dua deklarasi.Deklarasi default yang memungkinkan Anda untuk menentukan penutupan perbandingan:
Dan deklarasi kedua yang hanya mengambil parameter tunggal (array) dan "hardcoded untuk menggunakan komparator kurang dari."
Saya menguji versi modifikasi kode Anda di taman bermain dengan penutup ditambahkan sehingga saya bisa memantau fungsinya sedikit lebih dekat, dan saya menemukan bahwa dengan n set ke 1000, penutupan dipanggil sekitar 11.000 kali.
Ini bukan fungsi yang efisien, dan saya akan merekomendasikan menggunakan implementasi fungsi penyortiran yang lebih baik.
EDIT:
Saya melihat halaman wikipedia Quicksort dan menulis implementasi Swift untuk itu. Ini adalah program lengkap yang saya gunakan (di taman bermain)
Menggunakan ini dengan n = 1000, saya menemukan itu
Tampaknya metode pengurutan bawaan (atau hampir) pengurutan cepat, dan sangat lambat ...
sumber
2*n*log(n)
. Itu adalah 13815 perbandingan untuk menyortir n = 1000 elemen, jadi jika fungsi perbandingan dipanggil sekitar 11000 kali itu sepertinya tidak terlalu buruk.log(n)
untuk kompleksitas algoritmik secara konvensional mengacu pada log base-2. Alasan untuk tidak menyatakan dasar adalah bahwa perubahan hukum dasar untuk logaritma hanya memperkenalkan pengganda konstan, yang dibuang untuk keperluan notasi-O.C(n) = 2n ln n ≈ 1.39n log₂ n
. Untuk n = 1000 ini menghasilkan C (n) = 13815, dan ini bukan "notasi O besar".Pada Xcode 7 Anda dapat menghidupkan
Fast, Whole Module Optimization
. Ini akan segera meningkatkan kinerja Anda.sumber
Kinerja Swift Array ditinjau kembali:
Saya menulis tolok ukur saya sendiri membandingkan Swift dengan C / Objective-C. Tolok ukur saya menghitung bilangan prima. Ia menggunakan array bilangan prima sebelumnya untuk mencari faktor prima di setiap kandidat baru, sehingga cukup cepat. Namun, ia melakukan BANYAK membaca array, dan kurang menulis ke array.
Saya awalnya melakukan patokan ini terhadap Swift 1.2. Saya memutuskan untuk memperbarui proyek dan menjalankannya melawan Swift 2.0.
Proyek ini memungkinkan Anda memilih antara menggunakan array swift normal dan menggunakan buffer memori Swift yang tidak aman menggunakan semantik array.
Untuk C / Objective-C, Anda bisa memilih untuk menggunakan NSArrays, atau array C malloc'ed.
Hasil pengujian tampaknya sangat mirip dengan optimasi kode tercepat, terkecil ([-0s]) atau optimasi tercepat, agresif ([-0fast]).
Kinerja Swift 2.0 masih mengerikan dengan optimasi kode dimatikan, sedangkan kinerja C / Objective-C hanya lebih lambat.
Intinya adalah bahwa perhitungan berbasis array C malloc'd adalah yang tercepat, dengan margin sederhana
Swift dengan buffer yang tidak aman membutuhkan waktu sekitar 1,19X - 1,20X lebih lama dari array C malloc'd saat menggunakan optimasi kode tercepat dan terkecil. perbedaannya tampak sedikit kurang dengan optimasi yang cepat dan agresif (Swift membutuhkan waktu lebih lama dari 1,18x hingga 1,16x lebih lama dari C.
Jika Anda menggunakan array Swift biasa, perbedaan dengan C sedikit lebih besar. (Swift membutuhkan waktu ~ 1,22 hingga 1,23 lebih lama.)
Array Swift biasa
DRAMATICALLY
lebih cepat daripada yang ada di Swift 1.2 / Xcode 6. Performa mereka sangat dekat dengan array berbasis buffer Swift yang tidak aman sehingga menggunakan buffer memori yang tidak aman tidak terlalu lagi berarti masalah, yang besar.BTW, kinerja Objective-C NSArray bau. Jika Anda akan menggunakan objek penampung asli dalam kedua bahasa, Swift secara DRAMATICALLY lebih cepat.
Anda dapat memeriksa proyek saya di github di SwiftPerformanceBenchmark
Ini memiliki UI sederhana yang membuat pengumpulan statistik sangat mudah.
Sangat menarik bahwa menyortir tampaknya sedikit lebih cepat di Swift daripada di C sekarang, tetapi algoritma bilangan prima ini masih lebih cepat di Swift.
sumber
Masalah utama yang disebutkan oleh orang lain tetapi tidak cukup disebut adalah bahwa
-O3
tidak melakukan apa-apa di Swift (dan tidak pernah memiliki) sehingga ketika dikompilasi dengan itu secara efektif tidak dioptimalkan (-Onone
).Nama opsi telah berubah dari waktu ke waktu sehingga beberapa jawaban lain memiliki bendera yang sudah usang untuk opsi pembuatan. Pilihan saat ini yang benar (Swift 2.2) adalah:
Optimalisasi modul secara keseluruhan memiliki kompilasi yang lebih lambat tetapi dapat mengoptimalkan seluruh file dalam modul yaitu dalam setiap kerangka kerja dan dalam kode aplikasi yang sebenarnya tetapi tidak di antara mereka. Anda harus menggunakan ini untuk kinerja yang kritis)
Anda juga dapat menonaktifkan pemeriksaan keamanan untuk kecepatan yang lebih tinggi tetapi dengan semua pernyataan dan prasyarat tidak hanya dinonaktifkan tetapi dioptimalkan dengan dasar bahwa mereka benar. Jika Anda pernah menekan suatu pernyataan, ini berarti Anda melakukan perilaku yang tidak terdefinisi. Gunakan dengan sangat hati-hati dan hanya jika Anda menentukan bahwa peningkatan kecepatan bermanfaat untuk Anda (dengan menguji). Jika Anda merasa berharga untuk beberapa kode saya sarankan memisahkan kode itu ke dalam kerangka kerja yang terpisah dan hanya menonaktifkan pemeriksaan keamanan untuk modul itu.
sumber
Ini adalah Blog saya tentang Quick Sort- Contoh Github Quick-Sort
Anda dapat melihat algoritma pemartisian Lomuto di Mempartisi daftar. Ditulis dalam Swift.
sumber
Swift 4.1 memperkenalkan
-Osize
mode optimisasi baru .Bacaan lebih lanjut: https://swift.org/blog/osize/
sumber