Dalam salah satu artikel sebelumnya, saya berbicara tentang bagaimana Anda dapat membangun pelaksana program untuk mesin tumpukan virtual menggunakan pendekatan pemrograman fungsional dan berorientasi bahasa. Struktur matematika bahasa menyarankan struktur dasar untuk implementasi penerjemahnya, berdasarkan konsep semi-grup dan monoids. Pendekatan ini memungkinkan saya untuk membangun implementasi yang indah dan dapat diperluas dan untuk mematahkan tepuk tangan, tetapi pertanyaan pertama dari audiensi membuat saya keluar dari mimbar dan naik ke Emacs lagi.
Saya melakukan tes sederhana dan memastikan bahwa pada tugas-tugas sederhana yang hanya menggunakan stack, mesin virtual bekerja dengan cerdas, dan ketika menggunakan "memori" - sebuah array dengan akses acak - masalah besar dimulai. Bagaimana kami berhasil menyelesaikannya tanpa mengubah prinsip dasar arsitektur program dan mencapai percepatan ribuan kali program akan dibahas dalam artikel yang akan menarik perhatian Anda.
Haskell adalah bahasa khusus dengan ceruk khusus. Tujuan utama penciptaan dan pengembangannya adalah perlunya lingua franca untuk mengekspresikan dan menguji ide-ide pemrograman fungsional. Ini membenarkan fitur-fiturnya yang paling mencolok: kemalasan, kemurnian tertinggi, penekanan pada jenis dan manipulasi dengannya. Itu tidak dirancang untuk pengembangan sehari-hari, tidak untuk pemrograman industri, bukan untuk penggunaan luas. Fakta bahwa itu benar-benar digunakan untuk membuat proyek skala besar di industri jaringan dan dalam pemrosesan data adalah niat baik dari pengembang, bukti konsep, jika Anda mau. Namun sejauh ini, produk yang paling penting, banyak digunakan dan luar biasa kuat yang ditulis dalam Haskell adalah ... kompiler ghc. Dan ini sepenuhnya dibenarkan dari sudut pandang tujuannya - untuk menjadi alat untuk penelitian di bidang ilmu komputer. Prinsip yang diproklamirkan oleh Simon Payton-Johnson: "Hindari kesuksesan dengan segala cara" diperlukan agar bahasa tetap menjadi instrumen seperti itu. Haskell seperti ruang steril di laboratorium pusat penelitian yang mengembangkan teknologi semikonduktor atau nanomaterial. Sangat tidak nyaman untuk bekerja, dan untuk praktik sehari-hari itu juga membatasi kebebasan bertindak, tetapi tanpa ketidaknyamanan ini, tanpa kepatuhan tanpa kompromi terhadap pembatasan, tidak akan mungkin untuk mengamati dan mempelajari efek halus yang nantinya akan menjadi dasar perkembangan industri. Pada saat yang sama, dalam sterilitas industri hanya akan diperlukan dalam volume yang paling diperlukan, dan hasil percobaan ini akan muncul di kantong kita dalam bentuk gadget. Kita mempelajari bintang dan galaksi bukan karena kita berharap menerima manfaat langsung darinya, tetapi karena, pada skala objek-objek yang tidak praktis ini, efek kuantum dan relativistik menjadi dapat diamati dan dipelajari, sedemikian rupa sehingga nantinya kita dapat menggunakan pengetahuan ini untuk mengembangkan sesuatu yang sangat bermanfaat. Jadi Haskell dengan garis "salah", kemalasan perhitungan yang tidak praktis, kekakuan beberapa algoritma inferensi tipe, dengan kurva input yang sangat curam, akhirnya tidak memungkinkan Anda untuk dengan mudah membuat aplikasi pintar pada lutut atau sistem operasi. Namun, lensa, monad, parsing kombinatorial, meluasnya penggunaan monoids, metode pembuktian teorema otomatis, manajer paket fungsional deklaratif, tipe linier dan dependen mendekati dunia praktis. Aplikasi ini ditemukan dalam kondisi yang kurang steril dalam bahasa Python, Scala, Kotlin, F #, Rust, dan banyak lainnya. Tetapi saya tidak akan menggunakan salah satu dari bahasa yang luar biasa ini untuk mengajarkan prinsip-prinsip pemrograman fungsional: Saya akan membawa siswa ke laboratorium, menunjukkan cara kerjanya dalam contoh yang cerah dan bersih, dan kemudian Anda dapat melihat prinsip-prinsip ini bekerja di pabrik mesin yang besar dan tidak bisa dipahami, tetapi sangat cepat. Menghindari kesuksesan dengan segala cara adalah perlindungan terhadap upaya memasukkan pembuat kopi dalam mikroskop elektron untuk mempopulerkannya. Dan di kompetisi mana bahasa lebih keren, Haskell akan selalu berada di luar nominasi yang biasa.
Namun, orang itu lemah, dan iblis juga tinggal di dalam saya, yang membuat saya ingin membandingkan, mengevaluasi, dan membela "bahasa favorit saya" di depan orang lain. Jadi, setelah menulis implementasi mesin stacked yang elegan, berdasarkan komposisi monoid, dengan satu-satunya tujuan untuk melihat apakah ide ini bekerja untuk saya, saya langsung kesal karena saya menyadari bahwa implementasinya bekerja dengan cemerlang, tetapi sangat tidak efisien! Seolah-olah saya benar-benar akan menggunakannya untuk tugas-tugas serius, atau untuk menjual mesin bertumpuk saya di pasar yang sama di mana mesin virtual Python atau Java ditawarkan. Tapi sialnya, artikel tentang anak babi yang dengannya seluruh percakapan ini dimulai mulai memberikan angka-angka lezat: ratusan milidetik untuk anak babi itu, detik untuk Python ... dan monoid cantikku tidak dapat mengatasi tugas yang sama dalam satu jam! Saya harus berhasil! Mikroskop saya akan menyeduh espresso tidak lebih buruk daripada mesin kopi di lorong institut! Istana Kristal dapat tersebar dan diluncurkan ke luar angkasa!
Tapi apa yang Anda siap menyerah, malaikat matematika bertanya kepada saya? Kemurnian dan transparansi arsitektur istana? Fleksibilitas dan ekstensibilitas yang diberikan homomorfisme dari program ke solusi lain? Iblis dan malaikat itu keras kepala, dan penganut Tao yang bijak, yang saya juga izinkan untuk hidup, mengusulkan untuk mengambil jalan yang sesuai dengan keduanya, dan mengikutinya selama mungkin. Namun, tidak dengan tujuan mengidentifikasi pemenang, tetapi untuk mengetahui jalan itu sendiri, untuk mengetahui sejauh mana itu mengarah, dan untuk mendapatkan pengalaman baru. Jadi saya tahu kesedihan dan sukacita optimisasi yang sia-sia.
Sebelum kita mulai, kami menambahkan bahwa perbandingan bahasa dalam hal efektivitas tidak ada gunanya. Anda perlu membandingkan penerjemah (juru bahasa atau penyusun), atau kinerja seorang programmer yang menggunakan bahasa tersebut. Pada akhirnya, pernyataan bahwa program C adalah yang tercepat mudah disangkal dengan menulis juru bahasa C lengkap dalam BASIC, misalnya. Jadi, kami tidak membandingkan Haskell dan javascript, tetapi kinerja program yang dijalankan oleh penerjemah yang dikompilasi oleh ghc
dan program yang dijalankan, katakanlah, di browser tertentu. Semua terminologi babi berasal dari artikel inspirasional tentang mesin bertumpuk. Semua kode Haskell yang menyertai artikel dapat dipelajari di repositori .
Kami meninggalkan zona nyaman
Posisi awal akan menjadi implementasi mesin tumpukan monoid dalam bentuk EDSL - bahasa sederhana kecil yang memungkinkan menggabungkan dua lusin primitif untuk membuat program untuk mesin tumpukan virtual. Begitu dia masuk ke artikel kedua, kami memberinya nama monopig
. Hal ini didasarkan pada fakta bahwa bahasa untuk mesin yang ditumpuk membentuk sebuah monoid dengan operasi gabungan dan operasi kosong sebagai satu unit. Dengan demikian, ia sendiri dibangun dalam bentuk transformasi monoid dari kondisi mesin. Negara termasuk tumpukan, memori dalam bentuk vektor (struktur menyediakan akses acak ke elemen), bendera berhenti darurat, dan baterai monoid untuk mengumpulkan informasi debug. Seluruh struktur ini ditransmisikan di sepanjang rantai endomorfisme dari operasi ke operasi, melakukan proses komputasi. Struktur isomorfik kode program dibangun dari struktur yang dibentuk oleh program, dan darinya homomorfisme menjadi struktur berguna lainnya yang mewakili persyaratan program dalam hal jumlah argumen dan memori. Tahap akhir konstruksi adalah penciptaan monoids transformasi dalam kategori Claysley, yang memungkinkan Anda untuk merendam perhitungan dalam monad yang arbitrer. Jadi mesin mendapat kemampuan input-output dan perhitungan yang ambigu. Kami akan mulai dengan implementasi ini. Kode nya dapat ditemukan di sini .
Kami akan menguji efektivitas program pada implementasi naif dari saringan Eratosthenes, yang mengisi memori (array) dengan nol dan satu, yang menunjukkan bilangan prima dengan nol. Kami memberikan kode prosedural dari algoritma dalam javascript
:
var memSize = 65536; var arr = []; for (var i = 0; i < memSize; i++) arr.push(0); function sieve() { var n = 2; while (n*n < memSize) { if (!arr[n]) { var k = n; while (k < memSize) { k+=n; arr[k] = 1; } } n++; } }
Algoritma ini segera sedikit dioptimalkan. Ini menghilangkan berjalan buruk melalui sel-sel memori yang sudah terisi. Malaikat matematikawan saya tidak setuju dengan versi yang benar - benar naif dari contoh dalam proyek PorosenokVM
, karena pengoptimalan ini hanya memakan biaya lima instruksi bahasa tumpukan. Begini cara algoritma menerjemahkan ke monopig
:
sieve = push 2 <> while (dup <> dup <> mul <> push memSize <> lt) (dup <> get <> branch mempty fill <> inc) <> pop fill = dup <> dup <> add <> while (dup <> push memSize <> lt) (dup <> push 1 <> swap <> put <> exch <> add) <> pop
Dan di sini adalah bagaimana Anda dapat menulis implementasi yang setara dari algoritma ini pada Haskell idiomatik, menggunakan tipe yang sama seperti di monopig
:
sieve' :: Int -> Vector Int -> Vector Int sieve' km | k*k < memSize = sieve' (k+1) $ if m ! k == 0 then fill' k (2*k) m else m | otherwise = m fill' :: Int -> Int -> Vector Int -> Vector Int fill' knm | n < memSize = fill' k (n+k) $ m // [(n,1)] | otherwise = m
Menggunakan tipe Data.Vector
dan alat untuk bekerja dengannya, yang tidak terlalu umum untuk pekerjaan sehari-hari di Haskell. Ekspresi m ! k
m ! k
mengembalikan elemen k
dari vektor m
, dan m // [(n,1)]
- mengatur elemen dengan angka n
ke 1. Saya menulis ini di sini karena saya harus pergi ke mereka untuk meminta bantuan, walaupun saya bekerja di Haskell hampir setiap hari. Faktanya adalah bahwa struktur dengan akses acak dalam implementasi fungsional tidak efisien dan karena alasan ini tidak dicintai.
Menurut kondisi kompetisi yang ditentukan dalam artikel tentang anak babi, algoritme dijalankan 100 kali. Dan untuk menyingkirkan komputer tertentu, mari kita bandingkan kecepatan eksekusi ketiga program ini, merujuknya pada kinerja program javascript
yang dijalankan di Chrome.

Horor horor !!! monopig
tidak hanya memperlambat kecepatan tanpa dewa, jadi versi asli tidak jauh lebih baik! Haskell, tentu saja, keren, tapi tidak kalah dengan program yang berjalan di browser ?! Seperti yang pelatih ajarkan kepada kami, Anda tidak bisa hidup seperti itu, saatnya meninggalkan zona nyaman yang disediakan Haskell!
Atasi kemalasan
Mari kita perbaiki. Untuk melakukan ini, kompilasi program pada monopig
dengan flag -rtsopts
untuk -rtsopts
statistik run-time dan lihat apa yang perlu kita jalankan saringan Eratosthenes sekali:
$ ghc -O -rtsopts ./Monopig4.hs [1 of 1] Compiling Main ( Monopig4.hs, Monopig4.o ) Linking Monopig4 ... $ ./Monopig4 -RTS -sstderr "Ok" 68,243,040,608 bytes allocated in the heap 6,471,530,040 bytes copied during GC 2,950,952 bytes maximum residency (30667 sample(s)) 42,264 bytes maximum slop 15 MB total memory in use (7 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 99408 colls, 0 par 2.758s 2.718s 0.0000s 0.0015s Gen 1 30667 colls, 0 par 57.654s 57.777s 0.0019s 0.0133s INIT time 0.000s ( 0.000s elapsed) MUT time 29.008s ( 29.111s elapsed) GC time 60.411s ( 60.495s elapsed) <-- ! EXIT time 0.000s ( 0.000s elapsed) Total time 89.423s ( 89.607s elapsed) %GC time 67.6% (67.5% elapsed) Alloc rate 2,352,591,525 bytes per MUT second Productivity 32.4% of total user, 32.4% of total elapsed
Baris terakhir memberi tahu kita bahwa program ini bergerak dalam bidang komputasi produktif hanya sepertiga dari waktu. Selama sisa waktu itu, pengumpul sampah lari dari ingatan dan dibersihkan untuk perhitungan yang malas. Berapa kali kita diberitahu di masa kanak-kanak bahwa kemalasan tidak baik! Di sini, fitur utama Haskell membuat kami tidak puas, mencoba membuat beberapa miliar transformasi vektor dan tumpukan yang ditangguhkan.
Malaikat matematika pada saat ini mengangkat jarinya dan dengan senang hati berbicara tentang fakta bahwa sejak zaman Gereja Alonzo, ada teorema yang menyatakan bahwa strategi perhitungan tidak mempengaruhi hasil mereka, yang berarti bahwa kita bebas memilihnya karena alasan efisiensi. Mengubah perhitungan menjadi ketat tidak sulit sama sekali - beri tanda !
dalam deklarasi jenis stack dan memori, dan dengan demikian membuat bidang-bidang ini ketat.
data VM a = VM { stack :: !Stack , status :: Maybe String , memory :: !Memory , journal :: !a }
Kami tidak akan mengubah apa pun dan segera memeriksa hasilnya:
$ ./Monopig41 +RTS -sstderr "Ok" 68,244,819,008 bytes allocated in the heap 7,386,896 bytes copied during GC 528,088 bytes maximum residency (2 sample(s)) 25,248 bytes maximum slop 16 MB total memory in use (14 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 129923 colls, 0 par 0.666s 0.654s 0.0000s 0.0001s Gen 1 2 colls, 0 par 0.001s 0.001s 0.0006s 0.0007s INIT time 0.000s ( 0.000s elapsed) MUT time 13.029s ( 13.048s elapsed) GC time 0.667s ( 0.655s elapsed) EXIT time 0.001s ( 0.001s elapsed) Total time 13.700s ( 13.704s elapsed) %GC time 4.9% (4.8% elapsed) Alloc rate 5,238,049,412 bytes per MUT second Productivity 95.1% of total user, 95.1% of total elapsed
Produktivitas telah tumbuh secara signifikan. Total biaya memori masih tetap mengesankan karena data tidak dapat diubah, tetapi yang paling penting, sekarang kita telah membatasi kemalasan data, pengumpul sampah memiliki peluang untuk malas, hanya 5% pekerjaan yang tersisa. Masukkan entri baru di peringkat.

Nah, perhitungan yang ketat telah membawa kita lebih dekat ke kecepatan kode Haskell asli, yang memalukan melambat tanpa mesin virtual. Ini berarti bahwa biaya overhead menggunakan vektor yang tidak dapat diubah secara signifikan melebihi biaya pemeliharaan mesin yang ditumpuk. Dan ini berarti bahwa sudah waktunya untuk mengucapkan selamat tinggal pada kekekalan memori.
Membiarkan Perubahan Menjadi Hidup
Tipe Data.Vector
baik, tetapi menggunakannya, kami menghabiskan banyak waktu menyalin, atas nama menjaga kemurnian proses komputasi. Menggantinya dengan tipe Data.Vector.Unpacked
kita setidaknya menghemat kemasan struktur, tetapi ini tidak secara mendasar mengubah gambar. Solusi yang benar adalah dengan menghapus memori dari keadaan mesin dan memberikan akses ke vektor eksternal menggunakan kategori Kleisley. Pada saat yang sama, bersama dengan vektor murni, Anda dapat menggunakan apa yang disebut Data.Vector.Mutable
vektor (bisa berubah-ubah). Data.Vector.Mutable
.
Kami akan menghubungkan modul-modul yang sesuai dan memikirkan bagaimana menangani data yang bisa berubah dalam program fungsional yang bersih.
import Control.Monad.Primitive import qualified Data.Vector.Unboxed as V import qualified Data.Vector.Unboxed.Mutable as M
Jenis kotor ini seharusnya diisolasi dari masyarakat murni. Mereka terkandung dalam monad kelas PrimMonad
(ini termasuk ST
atau IO
), di mana program bersih hati-hati memasukkan instruksi untuk tindakan yang ditulis dalam bahasa fungsional kristal pada perkamen berharga. Dengan demikian, perilaku hewan-hewan haram ini ditentukan oleh skenario yang benar-benar ortodoks dan tidak berbahaya. Tidak semua program untuk mesin kami menggunakan memori, jadi tidak perlu mengutuk seluruh arsitektur untuk pencelupan di monad IO
. Seiring dengan subset bersih dari bahasa monopig
kami akan membuat empat instruksi yang memberikan akses ke memori, dan hanya mereka yang akan memiliki akses ke wilayah berbahaya.
Jenis mesin bersih semakin pendek:
data VM a = VM { stack :: !Stack , status :: Maybe String , journal :: !a }
Desainer program dan program itu sendiri hampir tidak akan melihat perubahan ini, tetapi tipenya akan berubah. Selain itu, masuk akal untuk mendefinisikan beberapa jenis sinonim untuk menyederhanakan tanda tangan.
type Memory m = M.MVector (PrimState m) Int type Logger ma = Memory m -> Code -> VM a -> m (VM a) type Program' ma = Logger ma -> Memory m -> Program ma
Konstruktor akan memiliki argumen lain yang mewakili akses memori. Pelaksana akan berubah secara signifikan, terutama mereka yang menyimpan log perhitungan, karena sekarang mereka perlu menanyakan keadaan vektor variabel untuk itu. Kode lengkap dapat dilihat dan dipelajari dalam repositori, tetapi di sini saya akan memberikan yang paling menarik: implementasi komponen dasar untuk bekerja dengan memori untuk menunjukkan bagaimana ini dilakukan.
geti :: PrimMonad m => Int -> Program' ma geti i = programM (GETI i) $ \mem -> \s -> if (0 <= i && i < memSize) then \vm -> do x <- M.unsafeRead mem i setStack (x:s) vm else err "index out of range" puti :: PrimMonad m => Int -> Program' ma puti i = programM (PUTI i) $ \mem -> \case (x:s) -> if (0 <= i && i < memSize) then \vm -> do M.unsafeWrite mem ix setStack s vm else err "index out of range" _ -> err "expected an element" get :: PrimMonad m => Program' ma get = programM (GET) $ \m -> \case (i:s) -> \vm -> do x <- M.read mi setStack (x:s) vm _ -> err "expected an element" put :: PrimMonad m => Program' ma put = programM (PUT) $ \m -> \case (i:x:s) -> \vm -> M.write mix >> setStack s vm _ -> err "expected two elemets"
Daemon optimizer segera menawarkan sedikit lebih banyak untuk memeriksa nilai indeks yang diperbolehkan dalam memori, karena untuk geti
puti
dan geti
indeks dikenal pada tahap pembuatan program dan nilai yang salah dapat dihilangkan terlebih dahulu. Indeks yang ditentukan secara dinamis untuk perintah put
dan get
tidak menjamin keamanan, dan malaikat matematika tidak mengizinkan panggilan berbahaya dilakukan kepada mereka.
Semua ini ribut dengan menempatkan memori ke dalam argumen terpisah yang tampaknya rumit. Tapi itu sangat jelas menunjukkan data yang akan diubah oleh tempat mereka - mereka harus di luar . Saya mengingatkan Anda bahwa kami mencoba membawa seorang pengantar pizza ke laboratorium steril. Fungsi murni tahu apa yang harus dilakukan dengan mereka, tetapi benda-benda ini tidak akan pernah menjadi warga negara kelas satu, dan tidak layak menyiapkan pizza tepat di laboratorium.
Mari kita periksa apa yang kita beli dengan perubahan ini:
$ ./Monopig5 +RTS -sstderr "Ok" 9,169,192,928 bytes allocated in the heap 2,006,680 bytes copied during GC 529,608 bytes maximum residency (2 sample(s)) 25,248 bytes maximum slop 2 MB total memory in use (0 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 17693 colls, 0 par 0.094s 0.093s 0.0000s 0.0001s Gen 1 2 colls, 0 par 0.000s 0.000s 0.0002s 0.0003s INIT time 0.000s ( 0.000s elapsed) MUT time 7.228s ( 7.232s elapsed) GC time 0.094s ( 0.093s elapsed) EXIT time 0.000s ( 0.000s elapsed) Total time 7.325s ( 7.326s elapsed) %GC time 1.3% (1.3% elapsed) Alloc rate 1,268,570,828 bytes per MUT second Productivity 98.7% of total user, 98.7% of total elapsed
Ini sudah berlangsung! Penggunaan memori berkurang delapan kali, kecepatan eksekusi program meningkat 180 kali, dan pengumpul sampah hampir sepenuhnya tidak bekerja.

Solusinya muncul monopig st. mut. , yang sepuluh kali lebih lambat dari solusi asli pada js
, tetapi selain itu, solusi asli pada Haskell, menggunakan vektor bisa berubah. Ini kodenya:
fill' :: Int -> Int -> Memory IO -> IO (Memory IO) fill' knm | n > memSize-k = return m | otherwise = M.unsafeWrite mn 1 >> fill' k (n+k) m sieve' :: Int -> Memory IO -> IO (Memory IO) sieve' km | k*k < memSize = do x <- M.unsafeRead mk if x == 0 then fill' k (2*k) m >>= sieve' (k+1) else sieve' (k+1) m | otherwise = return m
Dimulai sebagai berikut
main = do m <- M.replicate memSize 0 stimes 100 (sieve' 2 m >> return ()) print "Ok"
Dan sekarang Haskell akhirnya menunjukkan bahwa dia bukan bahasa mainan. Anda hanya perlu menggunakannya dengan bijak. Ngomong-ngomong, kode di atas menggunakan fakta bahwa tipe IO ()
membentuk semigroup dengan operasi eksekusi berurutan program (>>)
, dan dengan bantuan stimes
kami mengulangi 100 kali perhitungan masalah pengujian.
Sekarang sudah jelas mengapa ada ketidaksukaan terhadap array fungsional dan mengapa tidak ada yang ingat bagaimana bekerja dengan mereka: begitu seorang programmer Haskell benar-benar membutuhkan struktur akses acak, ia memfokuskan kembali pada data yang dapat diubah dan bekerja di monad ST atau IO.
Membawa sebagian perintah ke dalam zona khusus mempertanyakan legalitas isomorfisme programnya . Bagaimanapun, kita tidak dapat secara bersamaan mengubah kode menjadi program murni dan yang monadik, ini tidak memungkinkan sistem tipe untuk melakukannya. Namun, kelas tipe cukup fleksibel agar isomorfisma ini ada. Kode homomorfisme program ini sekarang dibagi menjadi beberapa homomorfisma untuk himpunan bagian bahasa yang berbeda. Bagaimana tepatnya ini dilakukan dapat dilihat pada [kode] () program secara lengkap.
Jangan berhenti di situ
Menghilangkan panggilan fungsi yang tidak perlu dan menyematkan kodenya secara langsung menggunakan pragma {-# INLINE #-}
akan membantu sedikit mengubah produktivitas program. Metode ini tidak cocok untuk fungsi rekursif, tetapi bagus untuk komponen dasar dan fungsi penyetel. Ini mengurangi waktu eksekusi program uji dengan 25% lainnya (lihat Monopig51.hs ).

Langkah masuk akal berikutnya adalah menyingkirkan alat logging ketika mereka tidak dibutuhkan. Pada tahap pembentukan endomorfisme yang mewakili program, kami menggunakan argumen eksternal, yang kami tentukan saat startup. program
konstruktor cerdas dan programM
dapat diperingatkan bahwa mungkin tidak ada argumen-logger. Dalam hal ini, kode konverter tidak mengandung sesuatu yang berlebihan: hanya fungsionalitas dan memeriksa status mesin.
program code f = programM code (const f) programM code f (Just logger) mem = Program . ([code],) . ActionM $ \vm -> case status vm of Nothing -> logger mem code =<< f mem (stack vm) vm _ -> return vm programM code f _ mem = Program . ([code],) . ActionM $ \vm -> case status vm of Nothing -> f mem (stack vm) vm _ -> return vm
Sekarang, menjalankan fungsi harus secara eksplisit menunjukkan ada atau tidaknya penebangan tidak menggunakan tidak none
rintisan, tetapi menggunakan jenis Maybe (Logger ma)
. Mengapa ini harus bekerja, karena jika ada logging atau tidak, komponen program akan mencari tahu "pada saat terakhir", sebelum eksekusi? Bukankah kode yang tidak perlu dijahit pada tahap penyusunan komposisi program? Haskell adalah bahasa yang malas dan di sini memainkannya ke tangan kita. Sebelum eksekusi kode final dioptimalkan untuk tugas tertentu. Pengoptimalan ini mengurangi waktu pelaksanaan program hingga 40% (lihat Monopig52.hs ).

Dengan ini, kami akan menyelesaikan pekerjaan untuk mempercepat babi monoid. Dia sudah berlari cukup cepat sehingga malaikat dan iblis bisa tenang. Ini, tentu saja, bukan C, kami masih menggunakan daftar bersih sebagai tumpukan, tetapi menggantinya dengan array akan mengarah pada penggalian kode secara menyeluruh, dan penolakan untuk menggunakan template yang elegan dalam definisi perintah dasar. Saya ingin bertahan dengan perubahan minimal, dan terutama pada tingkat jenis.
Beberapa masalah tetap ada dengan logging. Hitungan sederhana jumlah langkah atau menggunakan tumpukan berfungsi dengan baik (kami membuat bidang log menjadi ketat), tetapi memasangkannya sudah mulai memakan memori, Anda harus mengacaukan tendangan menggunakan seq
, yang sudah cukup mengganggu. Tapi katakan padaku, siapa yang akan mencatat 14 miliar langkah, jika Anda dapat men-debug tugas dalam ratusan pertama? Jadi saya tidak akan menghabiskan waktu untuk mempercepat akselerasi.
Tetap hanya menambahkan bahwa dalam artikel tentang anak babi, sebagai salah satu metode untuk mengoptimalkan perhitungan, pelacakan diberikan: alokasi bagian kode linier, jejak di mana perhitungan dapat dilakukan dengan melewati siklus pengiriman perintah utama (blok switch
). Dalam kasus kami, komposisi monoid komponen program menciptakan jejak seperti itu baik selama pembentukan program dari komponen EDSL, atau selama pengoperasian homomorfisme fromCode
. Metode pengoptimalan ini berlaku gratis untuk kami, dengan kata lain, dengan konstruksi.
Ada banyak solusi Conduits
dan cepat dalam ekosistem Haskell, seperti Conduits
atau Pipes
stream, ada penggantian String
sangat baik dan pencipta XML lincah seperti blaze-html, dan parser attoparsec
adalah standar untuk analisis kombinasi untuk tata bahasa LL (∞). Semua ini diperlukan untuk operasi normal. Tetapi yang lebih dibutuhkan adalah penelitian yang mengarah pada keputusan ini. Haskell telah dan tetap menjadi alat penelitian yang memenuhi persyaratan khusus yang tidak diperlukan oleh masyarakat umum. Saya melihat di Kamchatka bagaimana kartu As pada helikopter Mi-4 menutup kotak korek api pada suatu argumen, mendorong roda pendarat dengan roda sambil menggantung di udara. Ini bisa dilakukan, dan itu keren, tetapi tidak perlu.
Tapi, bagaimanapun, ini keren !!