λ -calculus: Apa yang paling efisien dalam representasi fungsi memori?


9

Saya ingin membandingkan kinerja fungsi yang dikodekan (Gereja / Scott) vs struktur data yang dikodekan secara klasik (assembler / C).

Tetapi sebelum saya melakukan itu, saya perlu tahu seberapa efisien fungsi representasi dalam memori. Fungsi ini tentu saja dapat diterapkan sebagian (alias penutupan).

Saya tertarik pada kedua algoritma pengkodean saat ini menggunakan bahasa fungsional populer (Haskell, ML) dan juga dalam yang paling efisien yang dapat dicapai.


Poin bonus: Apakah ada pengkodean seperti itu yang memetakan fungsi bilangan bulat yang disandikan ke bilangan bulat asli ( short, intdll dalam C). Apakah itu mungkin?


Saya menghargai efisiensi berdasarkan kinerja. Dengan kata lain semakin efisien pengkodean, semakin kecil pengaruhnya terhadap kinerja komputasi dengan struktur data fungsional.


Semua upaya Google saya gagal, mungkin saya tidak tahu kata kunci yang tepat.
Ford O.

Bisakah Anda mengedit pertanyaan untuk memperjelas apa yang Anda maksud dengan "efisien"? Efisien untuk apa? Ketika Anda meminta struktur data yang efisien, Anda perlu menentukan operasi apa yang ingin Anda lakukan pada struktur data, karena itu mempengaruhi pilihan struktur data. Atau maksud Anda bahwa pengkodean seefisien ruang mungkin?
DW

1
Ini cukup luas. Ada banyak mesin abstrak untuk kalkulus lambda yang bertujuan untuk mengeksekusinya secara efisien (lihat misalnya SECD, CAM, Krivine's, STG). Selain itu, Anda perlu mempertimbangkan data yang dikodekan Gereja / Scott, yang menimbulkan lebih banyak masalah. Misalnya dalam daftar Gereja yang dikodekan, operasi ekor haruslah O (n) dan bukan O (1). Saya pikir saya membaca di suatu tempat bahwa keberadaan pengkodean untuk daftar di Sistem F dengan operasi kepala dan ekor O (1) masih merupakan masalah terbuka.
chi

@ WD Saya berbicara tentang kinerja / overhead. Misalnya dengan pemetaan encoding yang efisien atas daftar gereja dan daftar Haskell harus mengambil waktu yang sama.
Ford O.

Kinerja untuk operasi apa? Apa yang ingin Anda lakukan dengan fungsinya? Apakah Anda ingin mengevaluasi fungsi-fungsi ini pada beberapa nilai? Pernah, atau mengevaluasi fungsi yang sama pada banyak nilai? Lakukan sesuatu yang lain dengan mereka? Apakah Anda hanya bertanya bagaimana cara mengkompilasi fungsi (ditulis dalam bahasa fungsional) sehingga dapat dieksekusi seefisien mungkin?
DW

Jawaban:


11

Masalahnya, tidak ada banyak kelonggaran dalam hal fungsi encoding. Berikut adalah opsi utama:

  • Penulisan ulang istilah: Anda menyimpan fungsi sebagai pohon sintaksis abstraknya (atau beberapa penyandiannya. Ketika Anda memanggil suatu fungsi, Anda secara manual melintasi hierarki pohon untuk mengganti parameternya dengan argumen. Ini mudah, tetapi sangat tidak efisien dalam hal waktu dan ruang .

  • Penutup: Anda memiliki beberapa cara untuk mewakili suatu fungsi, mungkin pohon sintaksis, kode mesin yang lebih mungkin. Dan dalam fungsi-fungsi ini, Anda merujuk argumen Anda dengan referensi dalam beberapa cara. Ini bisa berupa pointer-offset, bisa berupa integer atau indeks De Bruijn, bisa juga sebuah nama. Kemudian Anda mewakili fungsi sebagai penutup : fungsi "instruksi" (pohon, kode, dll.) Dipasangkan dengan struktur data yang berisi semua variabel bebas fungsi. Ketika suatu fungsi benar-benar diterapkan, entah bagaimana itu tahu bagaimana mencari variabel bebas dalam struktur datanya, menggunakan lingkungan, aritmatika pointer, dll.

Saya yakin ada opsi lain, tetapi ini adalah yang mendasar, dan saya kira hampir setiap opsi lain akan menjadi varian atau optimalisasi struktur penutupan dasar.

Jadi, dalam hal kinerja, penutupan hampir secara universal berkinerja lebih baik daripada istilah penulisan ulang. Dari variasi, mana yang lebih baik? Itu sangat tergantung pada bahasa dan arsitektur Anda, tetapi saya menduga "kode mesin dengan struct yang berisi vars gratis" adalah yang paling efisien. Ia memiliki semua yang dibutuhkan fungsi (instruksi dan nilai) dan tidak lebih, dan panggilan tidak berakhir dengan melakukan traversal besar-besaran.

Saya tertarik pada kedua algoritma pengkodean saat ini menggunakan bahasa fungsional populer (Haskell, ML)

Saya bukan ahli, tapi saya 99% kebanyakan rasa ML menggunakan beberapa variasi penutupan yang saya jelaskan, meskipun dengan beberapa optimasi mungkin. Lihat ini untuk perspektif (mungkin kedaluwarsa).

Haskell melakukan sesuatu yang sedikit lebih rumit karena evaluasi malas: ia menggunakan Penulisan Ulang Grafik Tanpa Tag Tanpa Batas .

dan juga yang paling efisien yang bisa dicapai.

Apa yang paling efisien? Tidak ada implementasi yang paling efisien di semua input, sehingga Anda mendapatkan implementasi yang rata-rata efisien, tetapi masing-masing akan unggul dalam skenario yang berbeda. Jadi tidak ada peringkat pasti yang paling atau paling tidak efisien.

Tidak ada keajaiban di sini. Untuk menyimpan suatu fungsi, Anda perlu menyimpan nilai-nilai gratisnya entah bagaimana, jika tidak Anda menyandikan informasi yang lebih sedikit daripada fungsi itu sendiri. Mungkin Anda dapat mengoptimalkan beberapa nilai gratis dengan evaluasi parsial tetapi berisiko untuk kinerja, dan Anda harus berhati-hati untuk memastikan bahwa ini selalu terhenti.

Dan, mungkin Anda bisa menggunakan semacam kompresi, atau algoritma pintar untuk mendapatkan efisiensi ruang. Tetapi kemudian Anda bisa memperdagangkan waktu untuk ruang, atau Anda berada dalam situasi di mana Anda telah mengoptimalkan untuk beberapa kasus dan melambat untuk yang lain.

Anda dapat mengoptimalkan untuk kasus umum, tapi apa kasus umum adalah dapat mengubah pada bahasa, area aplikasi, dll jenis kode yang cepat untuk video game (angka-angka, loop ketat dengan masukan yang besar) mungkin berbeda dari apa yang cepat untuk kompiler (traversal pohon, daftar kerja, dll.).

Poin bonus: Apakah ada pengkodean yang memetakan fungsi bilangan bulat yang disandikan ke bilangan bulat asli (pendek, int dll dalam C). Apakah itu mungkin?

Tidak, ini tidak mungkin. Masalahnya adalah bahwa kalkulus lambda tidak membiarkan Anda melakukan introspeksi. Ketika suatu fungsi mengambil argumen dengan tipe yang sama dengan angka Gereja, ia harus dapat menyebutnya, tanpa memeriksa definisi yang tepat dari angka itu. Itulah masalahnya dengan pengkodean Gereja: satu - satunya hal yang dapat Anda lakukan dengannya adalah memanggil mereka, dan Anda dapat mensimulasikan segala sesuatu yang berguna dengan ini, tetapi bukan tanpa biaya.

Lebih penting lagi, bilangan bulat menempati setiap kemungkinan penyandian biner. Jadi, jika lambda direpresentasikan sebagai bilangan bulat mereka, Anda tidak akan memiliki cara untuk mewakili lambda non-gereja! Atau, Anda akan memperkenalkan bendera untuk menunjukkan apakah lambda adalah angka atau bukan, tetapi efisiensi apa pun yang Anda inginkan mungkin hilang.

EDIT: Sejak menulis ini, saya menyadari opsi ketiga untuk mengimplementasikan fungsi tingkat tinggi: defungsionalisasi . Di sini, setiap panggilan fungsi berubah menjadi switchpernyataan besar , tergantung pada abstraksi lambda mana yang diberikan sebagai fungsi. Imbalannya di sini adalah bahwa ini merupakan transformasi seluruh program: Anda tidak dapat mengkompilasi bagian secara terpisah dan kemudian menghubungkannya dengan cara ini, karena Anda harus memiliki kumpulan lengkap abstraksi lambda sebelumnya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.