Ada 3 fungsi yang diberikan di bawah ini yang menemukan elemen terakhir tetapi kedua dalam daftar. Yang menggunakan last . init
tampaknya jauh lebih cepat daripada yang lain. Sepertinya saya tidak tahu mengapa.
Untuk pengujian, saya menggunakan daftar input [1..100000000]
(100 juta). Yang terakhir berjalan hampir secara instan sedangkan yang lain membutuhkan waktu beberapa detik.
-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"
-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse
-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init
init
telah dioptimalkan untuk menghindari "membongkar" daftar beberapa kali.myButLast
jauh lebih lambat? Tampaknya tidak membongkar daftar apa pun, tetapi hanya melewatinya sepertiinit
fungsinya ...[x, y]
adalah kependekan dari(x:(y:[]))
, jadi membongkar kontra luar, kontra kedua, dan memeriksa apakah ekor yang keduacons
adalah[]
. Selanjutnya klausa kedua akan membongkar daftar lagi di(x:xs)
. Ya itu membongkar cukup efisien, tetapi tentu saja jika itu terjadi sangat sering, itu akan memperlambat proses.init
tidak berulang kali memeriksa apakah argumennya adalah daftar tunggal atau daftar kosong. Setelah rekursi dimulai, itu hanya mengasumsikan bahwa elemen pertama akan ditempelkan ke hasil panggilan rekursif.myButLast
secara otomatis. Saya pikir itu lebih mungkin daftar fusi yang harus disalahkan untuk percepatan.Jawaban:
Saat mempelajari kecepatan dan pengoptimalan, sangat mudah untuk mendapatkan hasil yang sangat salah . Secara khusus, Anda tidak dapat benar-benar mengatakan bahwa satu varian lebih cepat daripada yang lain tanpa menyebutkan versi kompiler dan mode pengoptimalan pengaturan pembandingan Anda. Bahkan kemudian, prosesor modern sangat canggih untuk fitur prediktor cabang berbasis jaringan saraf, belum lagi semua jenis cache, sehingga, bahkan dengan pengaturan yang hati-hati, hasil pembandingan akan buram.
Yang telah dibilang...
Benchmarking adalah teman kita.
criterion
adalah paket yang menyediakan alat pembandingan lanjutan. Saya dengan cepat membuat tolok ukur seperti ini:Seperti yang Anda lihat, saya menambahkan varian yang secara eksplisit cocok dengan dua elemen sekaligus, tetapi sebaliknya itu adalah kode yang sama kata demi kata. Saya juga menjalankan tolok ukur secara terbalik, agar menyadari bias karena caching. Jadi, mari kita lari dan lihat!
Sepertinya versi "lambat" kami sama sekali tidak lambat! Dan seluk-beluk pencocokan pola tidak menambahkan apa pun. (Sedikit mempercepat kita melihat antara dua kali berturut-turut
match2
saya menganggap efek caching.)Ada cara untuk mendapatkan lebih banyak data "ilmiah" : kita dapat
-ddump-simpl
dan melihat cara kompiler melihat kode kita.Inspeksi struktur menengah adalah teman kita.
"Core" adalah bahasa internal GHC. Setiap file sumber Haskell disederhanakan menjadi Core sebelum diubah menjadi grafik fungsional akhir untuk dijalankan oleh sistem waktu. Jika kita melihat tahap menengah ini, ia akan memberi tahu kita
myButLast
danbutLast2
setara. Memang butuh melihat, karena, pada tahap penggantian nama, semua pengenal baik kami acak-acakan.Tampaknya
A1
danA4
yang paling mirip. Pemeriksaan menyeluruh akan menunjukkan bahwa memang struktur kodeA1
danA4
identik. ItuA2
danA3
sama juga masuk akal karena keduanya didefinisikan sebagai komposisi dua fungsi.Jika Anda akan memeriksa
core
output secara luas, masuk akal juga untuk menyediakan flag seperti-dsuppress-module-prefixes
dan-dsuppress-uniques
. Mereka membuatnya jauh lebih mudah dibaca.Daftar pendek musuh kita juga.
Jadi, apa yang bisa salah dengan pembandingan dan pengoptimalan?
ghci
, yang dirancang untuk permainan interaktif dan iterasi cepat, mengkompilasi sumber Haskell ke rasa kode byte tertentu, daripada dieksekusi akhir, dan menghindari optimasi mahal demi pemuatan ulang yang lebih cepat.Ini mungkin terlihat sedih. Tetapi sebenarnya bukan hal yang harus menjadi perhatian seorang programmer Haskell, sebagian besar waktu. Kisah nyata: Saya punya teman yang baru saja mulai belajar Haskell. Mereka telah menulis sebuah program untuk integrasi numerik, dan itu lambat sekali. Jadi kami duduk bersama dan menulis deskripsi kategoris dari algoritma, dengan diagram dan sebagainya. Ketika mereka menulis ulang kode untuk menyelaraskan dengan deskripsi abstrak, secara ajaib menjadi, seperti, cheetah cepat, dan langsing pada memori juga. Kami menghitung π dalam waktu singkat. Pesan moral dalam cerita? Struktur abstrak yang sempurna, dan kode Anda akan mengoptimalkan dirinya sendiri.
sumber
ghci
tampaknya memberikan hasil yang berbeda (dalam hal kecepatan) dibandingkan dengan membuat exe terlebih dahulu, seperti yang Anda katakan.