Perbedaan kinerja antara debug dan rilis build

280

Harus saya akui, bahwa biasanya saya tidak pernah repot untuk beralih antara konfigurasi Debug dan Release dalam program saya, dan saya biasanya memilih untuk pergi untuk konfigurasi Debug , bahkan ketika program-program tersebut sebenarnya digunakan di tempat pelanggan.

Sejauh yang saya tahu, satu-satunya perbedaan antara konfigurasi ini jika Anda tidak mengubahnya secara manual adalah bahwa Debug memiliki DEBUGkonstanta yang didefinisikan, dan Release telah memeriksa kode Optimize .

Jadi pertanyaan saya sebenarnya ada dua:

  1. Apakah ada banyak perbedaan kinerja antara kedua konfigurasi ini. Apakah ada jenis kode tertentu yang akan menyebabkan perbedaan besar dalam kinerja di sini, atau sebenarnya tidak terlalu penting?

  2. Apakah ada jenis kode yang akan berjalan dengan baik di bawah konfigurasi Debug yang mungkin gagal di bawah konfigurasi Release , atau dapatkah Anda yakin bahwa kode yang diuji dan bekerja dengan baik di bawah konfigurasi Debug juga akan bekerja dengan baik di bawah konfigurasi Release.

Øyvind Bråthen
sumber
1
Terkait: stackoverflow.com/questions/33871181/…
BlueRaja - Danny Pflughoeft

Jawaban:

511

Compiler C # itu sendiri tidak banyak mengubah IL yang dipancarkan dalam rilis build. Yang perlu dicatat adalah bahwa itu tidak lagi memancarkan opcode NOP yang memungkinkan Anda untuk mengatur breakpoint pada kurung kurawal. Yang besar adalah pengoptimal yang dibangun ke dalam kompiler JIT. Saya tahu itu membuat optimasi berikut:

  • Metode inlining. Panggilan metode diganti dengan menyuntikkan kode metode. Ini yang besar, itu membuat aksesor properti pada dasarnya gratis.

  • Alokasi register CPU. Variabel lokal dan argumen metode dapat tetap disimpan dalam register CPU tanpa pernah (atau kurang sering) disimpan kembali ke frame stack. Ini adalah yang besar, penting untuk menyulitkan debugging kode yang dioptimalkan. Dan memberi arti pada kata kunci yang mudah menguap .

  • Array eliminasi indeks memeriksa. Optimasi penting ketika bekerja dengan array (semua kelas koleksi .NET menggunakan array secara internal). Ketika kompiler JIT dapat memverifikasi bahwa loop tidak pernah mengindeks array dari batas maka itu akan menghilangkan pemeriksaan indeks. Yang besar.

  • Ulangi membuka gulungan. Loop dengan tubuh kecil ditingkatkan dengan mengulangi kode hingga 4 kali dalam tubuh dan looping lebih sedikit. Mengurangi biaya cabang dan meningkatkan opsi eksekusi skalar super-prosesor.

  • Penghapusan kode mati. Pernyataan seperti jika (false) {/ ... /} akan sepenuhnya dihilangkan. Ini dapat terjadi karena pelipatan dan pelurusan yang konstan. Kasus lain adalah ketika kompiler JIT dapat menentukan bahwa kode tidak memiliki efek samping yang mungkin. Optimalisasi inilah yang membuat kode profil sangat rumit.

  • Mengangkat kode. Kode di dalam loop yang tidak terpengaruh oleh loop dapat dipindahkan keluar dari loop. Pengoptimal dari kompiler C akan menghabiskan lebih banyak waktu untuk mencari peluang untuk diangkat. Namun ini adalah optimasi mahal karena analisis aliran data yang diperlukan dan jitter tidak mampu membayar waktu sehingga hanya mengangkat kasus yang jelas. Memaksa .NET programmer untuk menulis kode sumber yang lebih baik dan mengangkat sendiri.

  • Penghapusan sub-ekspresi umum. x = y + 4; z = y + 4; menjadi z = x; Cukup umum dalam pernyataan seperti dest [ix + 1] = src [ix + 1]; ditulis untuk keterbacaan tanpa memperkenalkan variabel pembantu. Tidak perlu kompromi keterbacaan.

  • Lipat konstan. x = 1 + 2; menjadi x = 3; Contoh sederhana ini ditangkap lebih awal oleh kompiler, tetapi terjadi pada waktu JIT ketika optimasi lain memungkinkan ini.

  • Salin propagasi. x = a; y = x; menjadi y = a; Ini membantu pengalokasi register membuat keputusan yang lebih baik. Ini adalah masalah besar dalam x86 jitter karena memiliki beberapa register yang dapat digunakan. Setelah memilih yang tepat sangat penting untuk kinerja.

Ini adalah optimasi yang sangat penting yang dapat membuat besar kesepakatan perbedaan ketika, misalnya, Anda profil Debug membangun aplikasi Anda dan bandingkan dengan Release membangun. Itu hanya benar-benar penting meskipun ketika kode berada di jalur kritis Anda, 5 hingga 10% dari kode yang Anda tulis yang benar - benar mempengaruhi kinerja program Anda. Pengoptimal JIT tidak cukup pintar untuk mengetahui apa yang penting di muka, itu hanya dapat menerapkan tombol "putar ke sebelas" untuk semua kode.

Hasil efektif dari optimasi ini pada waktu eksekusi program Anda sering dipengaruhi oleh kode yang berjalan di tempat lain. Membaca file, mengeksekusi query dbase, dll. Membuat pekerjaan pengoptimal JIT benar-benar tidak terlihat. Itu tidak masalah :)

Pengoptimal JIT adalah kode yang cukup andal, sebagian besar karena telah diuji jutaan kali. Sangat jarang memiliki masalah dalam versi rilis program Anda. Namun itu terjadi. Kegelisahan x64 dan x86 memiliki masalah dengan struct. Jitter x86 memiliki masalah dengan konsistensi titik mengambang, menghasilkan hasil yang sedikit berbeda ketika perantara perhitungan titik mengambang disimpan dalam register FPU pada presisi 80-bit alih-alih terpotong saat disiram ke memori.

Hans Passant
sumber
23
Saya tidak berpikir semua koleksi menggunakan array: LinkedList<T>tidak, meskipun itu tidak sering digunakan.
svick
Saya pikir CLR mengkonfigurasi FPU ke presisi 53-bit (pencocokan ganda lebar 64-bit), jadi seharusnya tidak ada perhitungan ganda diperpanjang 80-bit untuk nilai Float64. Namun, perhitungan Float32 dapat dihitung pada ketepatan 53-bit ini dan hanya terpotong saat disimpan ke memori.
Gubernur
2
Kata volatilekunci tidak berlaku untuk variabel lokal yang disimpan dalam bingkai tumpukan. Dari dokumentasi di msdn.microsoft.com/en-us/library/x13ttww7.aspx : "Kata kunci yang mudah menguap hanya dapat diterapkan ke bidang kelas atau struct. Variabel lokal tidak dapat dinyatakan volatil."
Kris Vandermotten
8
sebagai perubahan sederhana, saya kira apa yang benar-benar membuat perbedaan antara Debugdan Releasemembangun dalam hal ini adalah kotak centang "optimalkan kode" yang biasanya aktif Releasetetapi mati untuk Debug. Hanya untuk memastikan pembaca tidak mulai berpikir bahwa ada "keajaiban", perbedaan tak terlihat antara dua konfigurasi build yang melampaui apa yang ditemukan di halaman properti proyek di Visual Studio.
chiccodoro
3
Mungkin perlu disebutkan bahwa hampir tidak ada metode pada System.Diagnostics.Debug melakukan apa pun dalam membangun debug. Juga variabel tidak diselesaikan dengan cepat, lihat ( stackoverflow.com/a/7165380/20553 ).
Martin Brown
23
  1. Ya, ada banyak perbedaan kinerja dan ini benar-benar berlaku di seluruh kode Anda. Debug melakukan sangat sedikit optimasi kinerja, dan mode rilis sangat banyak;

  2. Hanya kode yang bergantung pada DEBUGkonstanta yang dapat bekerja secara berbeda dengan rilis build. Selain itu, Anda seharusnya tidak melihat masalah.

Contoh kode kerangka kerja yang bergantung pada DEBUGkonstanta adalah Debug.Assert()metode, yang atributnya [Conditional("DEBUG)"]didefinisikan. Ini berarti bahwa itu juga tergantung pada DEBUGkonstanta dan ini tidak termasuk dalam rilis rilis.

Pieter van Ginkel
sumber
2
Ini semua benar, tetapi bisakah Anda mengukur perbedaan? Atau perhatikan perbedaan saat menggunakan program? Tentu saja saya tidak ingin mendorong siapa pun untuk merilis perangkat lunak mereka dalam mode debug, tetapi pertanyaannya adalah apakah ada perbedaan kinerja yang sangat besar dan saya tidak dapat melihatnya.
testalino
2
Juga patut dicatat adalah bahwa versi debug berkorelasi dengan kode sumber asli ke tingkat yang jauh lebih tinggi daripada versi rilis. Jika Anda berpikir (namun tidak mungkin) bahwa seseorang mungkin mencoba untuk merekayasa balik executable Anda, Anda tidak ingin membuatnya lebih mudah dengan menggunakan versi debug.
jwheron
2
@ testalino - Yah, hari ini sulit. Prosesor berjalan sangat cepat sehingga pengguna hampir tidak menunggu proses untuk benar-benar mengeksekusi kode karena tindakan pengguna, jadi ini semua relatif. Namun, jika Anda benar-benar melakukan proses panjang, ya Anda akan perhatikan. Kode berikut misalnya berjalan 40% lebih lambat di bawah DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel
2
Juga, jika Anda asp.netmenggunakan dan menggunakan debug alih-alih melepaskan beberapa skrip mungkin ditambahkan pada halaman Anda, seperti: MicrosoftAjax.debug.jsyang memiliki sekitar 7k baris.
BrunoLM
13

Ini sangat tergantung pada sifat aplikasi Anda. Jika aplikasi Anda berat-UI, Anda mungkin tidak akan melihat perbedaan karena komponen paling lambat yang terhubung ke komputer modern adalah pengguna. Jika Anda menggunakan beberapa animasi UI, Anda mungkin ingin menguji apakah Anda dapat melihat kelambatan yang terlihat saat berjalan di DEBUG build.

Namun, jika Anda memiliki banyak perhitungan yang berat perhitungan, maka Anda akan melihat perbedaan (bisa setinggi 40% seperti yang disebutkan @Pieter, meskipun itu akan tergantung pada sifat perhitungannya).

Ini pada dasarnya adalah tradeoff desain. Jika Anda merilis di bawah DEBUG build, maka jika pengguna mengalami masalah, Anda bisa mendapatkan traceback yang lebih bermakna dan Anda dapat melakukan diagnostik yang jauh lebih fleksibel. Dengan melepaskan DEBUG build, Anda juga menghindari pengoptimal yang menghasilkan Heisenbugs yang tidak jelas .

Lie Ryan
sumber
11
  • Pengalaman saya adalah bahwa aplikasi berukuran sedang atau lebih besar terasa lebih responsif dalam rilis Rilis. Cobalah dengan aplikasi Anda dan lihat bagaimana rasanya.

  • Satu hal yang dapat menggigit Anda dengan rilis build adalah bahwa kode build Debug kadang-kadang dapat menekan kondisi ras dan bug lain yang terkait dengan threading. Kode yang dioptimalkan dapat menghasilkan penyusunan ulang instruksi dan eksekusi yang lebih cepat dapat memperburuk kondisi balapan tertentu.

Dan Bryant
sumber
9

Anda seharusnya tidak pernah merilis .NET Debug build ke dalam produksi. Mungkin berisi kode jelek untuk mendukung Edit-dan-Lanjutkan atau siapa yang tahu apa lagi. Sejauh yang saya tahu, ini hanya terjadi di VB bukan C # (catatan: posting asli ditandai C #) , tetapi harus tetap memberikan alasan untuk berhenti sejenak tentang apa yang menurut Microsoft mereka boleh lakukan dengan membangun Debug. Bahkan, sebelum .NET 4.0, kode VB bocor memori sebanding dengan jumlah instance objek dengan peristiwa yang Anda buat dalam mendukung Edit-dan-Lanjutkan. (Meskipun ini dilaporkan diperbaiki per https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , kode yang dihasilkan terlihat jahat, membuat WeakReferenceobjek dan menambahkannya ke daftar statis sementaramemegang kunci) Saya tentu tidak menginginkan dukungan debugging semacam ini di lingkungan produksi!

Jason Kresowaty
sumber
Saya telah merilis build Debug berkali-kali, dan tidak pernah melihat masalah. Satu-satunya perbedaan mungkin, adalah bahwa aplikasi sisi server kami bukan aplikasi web yang mendukung banyak pengguna. Tetapi ini adalah aplikasi sisi server dengan beban pemrosesan yang sangat tinggi. Dari pengalaman saya, perbedaan antara Debug dan Rilis tampaknya sepenuhnya teoretis. Saya belum pernah melihat perbedaan praktis dengan aplikasi kami.
Sam Goldberg
5

Dalam pengalaman saya, hal terburuk yang telah keluar dari mode Release adalah "bug rilis" yang tidak jelas. Karena IL (bahasa perantara) dioptimalkan dalam mode Rilis, ada kemungkinan bug yang tidak akan terwujud dalam mode Debug. Ada pertanyaan SO lainnya yang mencakup masalah ini: Alasan umum untuk bug dalam versi rilis tidak hadir dalam mode debug

Ini terjadi pada saya sekali atau dua kali di mana aplikasi konsol sederhana akan berjalan dengan baik dalam mode Debug, tetapi diberi input yang sama persis, akan keluar dalam mode rilis. Bug ini sangat sulit di-debug (dengan definisi mode Release, ironisnya).

Roly
sumber
Untuk menindaklanjuti, inilah artikel yang memberikan contoh Bug Rilis: codeproject.com/KB/trace/ReleaseBug.aspx
Roly
Masih merupakan masalah jika aplikasi diuji dan disetujui dengan pengaturan Debug, bahkan jika itu menekan kesalahan, jika itu menyebabkan rilis rilis gagal selama penyebaran.
Øyvind Bråthen
4

Saya akan mengatakan bahwa 1) sangat tergantung pada implementasi Anda. Biasanya, perbedaannya tidak terlalu besar. Saya melakukan banyak pengukuran dan seringkali saya tidak melihat perbedaan. Jika Anda menggunakan kode yang tidak dikelola, banyak array besar dan hal-hal seperti itu, perbedaan kinerja sedikit lebih besar, tetapi bukan dunia yang berbeda (seperti di C ++). 2) Biasanya dalam kode rilis lebih sedikit kesalahan ditampilkan (toleransi yang lebih tinggi), maka switch harus berfungsi dengan baik.

testalino
sumber
1
Untuk kode yang terikat IO, build rilis dapat dengan mudah menjadi debug yang lebih cepat.
Richard
0
    **Debug Mode:**
    Developer use debug mode for debugging the web application on live/local server. Debug mode allow developers to break the execution of program using interrupt 3 and step through the code. Debug mode has below features:
   1) Less optimized code
   2) Some additional instructions are added to enable the developer to set a breakpoint on every source code line.
   3) More memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are not cached.
   5) It has big size, and runs slower.

    **Release Mode:**
    Developer use release mode for final deployment of source code on live server. Release mode dlls contain optimized code and it is for customers. Release mode has below features:
   1) More optimized code
   2) Some additional instructions are removed and developer cant set a breakpoint on every source code line.
   3) Less memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.
Nandha kumar
sumber
2
sepertinya dalam mode rilis kadang-kadang elemen pertama daftar tidak diberi nomor dengan benar. Juga beberapa elemen dalam daftar digandakan. :)
Gian Paolo