Saya menemukan pertanyaan ini tentang bahasa mana yang mengoptimalkan rekursi ekor. Mengapa C # tidak mengoptimalkan rekursi ekor, jika memungkinkan?
Untuk kasus konkret, mengapa metode ini tidak dioptimalkan menjadi satu loop ( Visual Studio 2008 32-bit, jika itu penting) ?:
private static void Foo(int i)
{
if (i == 1000000)
return;
if (i % 100 == 0)
Console.WriteLine(i);
Foo(i+1);
}
c#
.net
optimization
tail-recursion
ripper234
sumber
sumber
preemptive
(misalnya algoritma faktorial) danNon-preemptive
(misalnya fungsi ackermann). Penulis hanya memberikan dua contoh yang telah saya sebutkan tanpa memberikan alasan yang tepat di balik percabangan ini. Apakah percabangan ini sama dengan fungsi rekursif ekor dan non-ekor?Jawaban:
Kompilasi JIT adalah tindakan penyeimbangan yang rumit antara tidak menghabiskan terlalu banyak waktu melakukan fase kompilasi (sehingga memperlambat aplikasi yang berumur pendek) vs. tidak melakukan analisis yang cukup untuk menjaga aplikasi tetap kompetitif dalam jangka panjang dengan kompilasi standar sebelumnya. .
Menariknya, langkah kompilasi NGen tidak ditargetkan untuk lebih agresif dalam pengoptimalannya. Saya menduga ini karena mereka tidak ingin memiliki bug di mana perilakunya bergantung pada apakah JIT atau NGen bertanggung jawab atas kode mesin.
The CLR itu sendiri tidak mendukung ekor optimasi panggilan, tetapi compiler bahasa-spesifik harus tahu bagaimana untuk menghasilkan relevan opcode dan JIT harus bersedia untuk menghormatinya. F #'s fsc akan menghasilkan opcodes yang relevan (meskipun untuk rekursi sederhana itu mungkin hanya mengubah semuanya menjadi
while
loop secara langsung). Csc C # tidak.Lihat posting blog ini untuk beberapa detail (sangat mungkin sekarang sudah ketinggalan zaman mengingat perubahan JIT baru-baru ini). Perhatikan bahwa CLR berubah untuk 4.0 , x86, x64 dan ia64 akan menghormati itu .
sumber
Ini Pengiriman umpan balik Microsoft Connect harus menjawab pertanyaan Anda. Ini berisi tanggapan resmi dari Microsoft, jadi saya akan merekomendasikan untuk melakukannya.
By the way, seperti yang telah ditunjukkan, perlu dicatat bahwa rekursi ekor yang dioptimalkan pada x64.
sumber
C # tidak mengoptimalkan rekursi panggilan-ekor karena untuk itulah F #!
Untuk mengetahui lebih mendalam tentang kondisi yang mencegah compiler C # melakukan pengoptimalan panggilan-ekor, lihat artikel ini: Kondisi panggilan-ekor JIT CLR .
Interoperabilitas antara C # dan F #
C # dan F # saling beroperasi dengan sangat baik, dan karena .NET Common Language Runtime (CLR) dirancang dengan interoperabilitas ini, setiap bahasa dirancang dengan pengoptimalan yang khusus untuk maksud dan tujuannya. Untuk contoh yang menunjukkan betapa mudahnya memanggil kode F # dari kode C #, lihat Memanggil kode F # dari kode C # ; untuk contoh panggilan fungsi C # dari kode F #, lihat Memanggil fungsi C # dari F # .
Untuk mendelegasikan interoperabilitas, lihat artikel ini: Mendelegasikan interoperabilitas antara F #, C # dan Visual Basic .
Perbedaan teoritis dan praktis antara C # dan F #
Berikut adalah artikel yang membahas beberapa perbedaan dan menjelaskan perbedaan desain rekursi tail-call antara C # dan F #: Menghasilkan Opcode Tail-Call di C # dan F # .
Berikut adalah artikel dengan beberapa contoh di C #, F #, dan C ++ \ CLI: Petualangan di Rekursi Ekor di C #, F #, dan C ++ \ CLI
Perbedaan teoritis utama adalah bahwa C # dirancang dengan loop sedangkan F # dirancang berdasarkan prinsip kalkulus Lambda. Untuk buku yang sangat bagus tentang prinsip kalkulus Lambda, lihat buku gratis ini: Structure and Interpretation of Computer Programs, oleh Abelson, Sussman, dan Sussman .
Untuk artikel pengantar yang sangat bagus tentang tail call di F #, lihat artikel ini: Pengenalan Mendetail tentang Tail Calls di F # . Terakhir, berikut adalah artikel yang membahas perbedaan antara rekursi non-ekor dan rekursi panggilan-ekor (di F #): Rekursi ekor vs. rekursi non-ekor di F tajam .
sumber
Saya baru-baru ini diberitahu bahwa kompilator C # untuk 64 bit mengoptimalkan rekursi ekor.
C # juga menerapkan ini. Alasan mengapa tidak selalu diterapkan, adalah karena aturan yang digunakan untuk menerapkan rekursi ekor sangat ketat.
sumber
Anda dapat menggunakan teknik trampolin untuk fungsi rekursif-ekor di C # (atau Java). Namun, solusi yang lebih baik (jika Anda hanya peduli tentang pemanfaatan tumpukan) adalah dengan menggunakan metode pembantu kecil ini untuk membungkus bagian dari fungsi rekursif yang sama dan membuatnya berulang sambil menjaga agar fungsi tetap dapat dibaca.
sumber
Seperti jawaban lain yang disebutkan, CLR memang mendukung pengoptimalan panggilan ekor dan tampaknya itu sedang dalam perbaikan progresif secara historis. Tetapi mendukungnya di C # memiliki
Proposal
masalah terbuka di repositori git untuk desain bahasa pemrograman C # Mendukung rekursi ekor # 2544 .Anda dapat menemukan beberapa detail dan info berguna di sana. Misalnya @jaykrell disebutkan
Juga izinkan saya menyebutkan (sebagai info tambahan), Ketika kami membuat lambda yang dikompilasi menggunakan kelas ekspresi di
System.Linq.Expressions
namespace, ada argumen bernama 'tailCall' yang seperti yang dijelaskan dalam komentarnya.Saya belum mencobanya, dan saya tidak yakin bagaimana ini dapat membantu terkait dengan pertanyaan Anda, tetapi Mungkin seseorang dapat mencobanya dan mungkin berguna dalam beberapa skenario:
sumber