Apakah ini benar-benar optimasi menonaktifkan praktik yang baik selama fase pengembangan dan debugging?

15

Saya telah membaca Pemrograman 16-Bit PIC Microcontrollers di C , dan ada penegasan ini dalam buku:

Namun, selama fase pengembangan dan debugging suatu proyek, selalu merupakan praktik yang baik untuk menonaktifkan semua optimisasi karena mereka dapat memodifikasi struktur kode yang dianalisis dan membuat penempatan langkah-tunggal dan penempatan breakpoint bermasalah.

Saya akui saya agak bingung. Saya tidak mengerti apakah penulis mengatakan itu karena periode evaluasi C30 atau apakah itu benar-benar praktik yang baik.

Saya ingin tahu apakah Anda benar-benar menggunakan latihan ini, dan mengapa?

Daniel Grillo
sumber

Jawaban:

16

Ini cukup standar dalam rekayasa perangkat lunak secara keseluruhan - ketika Anda mengoptimalkan kode, kompiler diperbolehkan untuk mengatur kembali hal-hal yang diinginkan, selama Anda tidak dapat membedakan operasi. Jadi, misalnya, jika Anda menginisialisasi variabel di dalam setiap iterasi dari sebuah loop, dan tidak pernah mengubah variabel di dalam loop, optimizer diperbolehkan untuk memindahkan inisialisasi keluar dari loop, sehingga Anda tidak membuang waktu dengannya.

Mungkin juga menyadari bahwa Anda menghitung angka yang kemudian Anda tidak melakukan apa pun dengan sebelum menulis. Dalam hal ini, itu mungkin menghilangkan perhitungan yang tidak berguna.

Masalah dengan optimasi adalah Anda ingin meletakkan breakpoint pada beberapa kode, yang telah dipindahkan atau dihilangkan oleh optimizer. Dalam hal ini, debugger tidak dapat melakukan apa yang Anda inginkan (umumnya, itu akan menempatkan breakpoint di suatu tempat dekat). Jadi, untuk membuat kode yang dihasilkan lebih mirip dengan apa yang Anda tulis, Anda mematikan optimisasi selama debug - ini memastikan bahwa kode yang ingin Anda hentikan benar-benar ada.

Anda harus berhati-hati dengan ini, karena tergantung pada kode Anda, optimasi dapat merusak banyak hal! Secara umum, kode yang dipecah oleh pengoptimal yang berfungsi dengan benar sebenarnya hanyalah kode buggy yang lolos dengan sesuatu, jadi Anda biasanya ingin mencari tahu mengapa pengoptimal merusaknya.

Michael Kohne
sumber
5
Argumen yang berlawanan dengan argumen ini adalah bahwa pengoptimal kemungkinan akan membuat hal-hal lebih kecil dan / atau lebih cepat, dan jika Anda punya kode terbatas waktu atau ukuran maka Anda dapat memecahkan sesuatu dengan menonaktifkan optimasi, dan buang waktu Anda debugging masalah yang tidak benar-benar ada. Tentu saja, debugger dapat membuat kode Anda lebih lambat dan lebih besar juga.
Kevin Vermeer
Di mana saya bisa belajar lebih banyak tentang ini?
Daniel Grillo
Saya belum pernah bekerja dengan kompiler C30 tetapi untuk kompiler C18 ada catatan aplikasi / manual untuk kompiler yang membahas optimasi apa yang didukungnya.
Markus
@O Engenheiro: Periksa dokumen kompiler Anda untuk optimasi apa yang didukungnya. Optimalisasi sangat bervariasi tergantung pada kompiler, perpustakaan, dan arsitektur target.
Michael Kohne
Sekali lagi, bukan untuk kompiler C30, tetapi gcc menerbitkan daftar PANJANG dari berbagai optimasi yang dapat diterapkan. Anda juga dapat menggunakan daftar ini untuk mendapatkan optimasi berbutir halus, jika Anda memiliki struktur kontrol tertentu yang Anda ingin tetap utuh. Daftarnya ada di sini: gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Kevin Vermeer
7

Saya sudah mengirim pertanyaan ini ke Jack Ganssle dan inilah yang dia jawab:

Daniel,

Saya lebih suka melakukan debug menggunakan optimasi apa pun yang ada dalam kode yang dirilis. NASA mengatakan "uji apa yang Anda terbang, apa yang Anda uji." Dengan kata lain, jangan lakukan pengujian dan kemudian ubah kodenya!

Namun, terkadang seseorang harus mematikan optimisasi agar debugger akan berfungsi. Saya mencoba mematikannya hanya dalam modul yang saya kerjakan. Untuk alasan itu saya percaya menyimpan file kecil, katakan beberapa ratus baris kode atau lebih.

Yang terbaik, Jack

Daniel Grillo
sumber
Saya pikir ada perbedaan yang tidak disebutkan antara dua paragraf dalam tanggapan ini. Pengujian mengacu pada prosedur yang seharusnya menunjukkan bahwa perangkat lunak, keras, dan / atau keras bekerja dengan baik. Debugging adalah proses di mana kode dilangkahi melalui instruksi demi instruksi untuk melihat mengapa kode tersebut tidak berfungsi [belum].
Kevin Vermeer
Sangat menyenangkan memiliki pilihan. Jadi pengujian dapat mencakup lebih banyak varietas / permutasi dengan dan tanpa optimasi. Semakin banyak cakupan, semakin baik pengujian
@reemrevnivek, saat Anda sedang debug, bukankah Anda juga menguji?
Daniel Grillo
@O Engenheiro - Tidak. Saya hanya debug jika tes gagal.
Kevin Vermeer
6

Tergantung, dan ini umumnya berlaku untuk semua alat bukan hanya C30.

Optimasi sering kali menghapus dan / atau merestrukturisasi kode dengan berbagai cara. Pernyataan peralihan Anda mungkin diterapkan kembali dengan konstruksi if / else atau dalam beberapa kasus dapat dihapus secara bersamaan. y = x * 16 dapat diganti dengan serangkaian shift kiri, dll. meskipun jenis optimisasi terakhir ini biasanya masih dapat dilewati, sebagian besar merupakan restrukturisasi pernyataan kontrol yang dapat Anda lakukan.

Ini dapat membuat tidak mungkin untuk melangkah debugger melalui kode C Anda karena struktur yang Anda tetapkan dalam C tidak ada lagi, mereka diganti atau dipesan ulang oleh kompiler menjadi sesuatu yang diyakini kompiler akan lebih cepat atau menggunakan lebih sedikit ruang. Hal ini juga dapat membuat breakpoints mustahil untuk ditetapkan dari daftar C karena instruksi yang Anda hentikan mungkin tidak ada lagi. Sebagai contoh, Anda dapat mencoba untuk menetapkan breakpoint di dalam pernyataan if, tetapi kompiler mungkin telah menghapus itu jika. Anda dapat mencoba mengatur breakpoint di dalam while atau for loop tetapi kompiler memutuskan untuk membuka gulungan itu sehingga tidak ada lagi.

Untuk alasan ini jika Anda dapat men-debug dengan optimisasi tidak aktif, biasanya lebih mudah. Anda harus selalu menguji ulang dengan optimisasi aktif. Ini adalah satu-satunya cara Anda akan menemukan bahwa Anda melewatkan sesuatu yang penting volatiledan menyebabkan kegagalan intermiten (atau keanehan lainnya).

Dalam kasus pengembangan yang disematkan, Anda harus berhati-hati dengan optimasi. Khususnya di bagian kode yang kritis waktu, beberapa menyela misalnya. Dalam kasus ini Anda harus mengkode bit kritis dalam perakitan atau menggunakan arahan kompiler untuk memastikan bagian ini tidak dioptimalkan sehingga Anda tahu mereka memiliki waktu eksekusi yang tetap atau waktu run kasus terburuk yang diperbaiki.

Gotcha lainnya dapat memasukkan kode ke dalam UC, Anda mungkin perlu optimasi kepadatan kode untuk memasukkan kode Anda ke dalam chip. Ini adalah salah satu alasan mengapa biasanya ide yang baik untuk memulai dengan kapasitas ROM terbesar UC dalam keluarga dan hanya memilih yang lebih kecil untuk pembuatan, setelah kode Anda dikunci.

Menandai
sumber
5

Secara umum, saya akan melakukan debug dengan pengaturan apa pun yang saya rencanakan untuk dirilis. Jika saya akan merilis kode yang dioptimalkan, saya akan debug dengan kode yang dioptimalkan. Jika saya akan merilis kode yang tidak dioptimalkan, saya akan men-debug dengan kode yang tidak dioptimalkan. Saya melakukan ini karena dua alasan. Pertama, pengoptimal dapat membuat perbedaan waktu yang cukup signifikan untuk menyebabkan produk akhir berperilaku berbeda dari kode yang tidak dioptimalkan. Kedua, meskipun sebagian besar cukup bagus, vendor kompiler melakukan kesalahan dan kode yang dioptimalkan dapat menghasilkan hasil yang berbeda dari kode yang tidak dioptimalkan. Sebagai hasilnya, saya ingin mendapatkan waktu tes sebanyak mungkin dengan pengaturan apa pun yang saya rencanakan untuk rilis.

Karena itu, pengoptimalisasi dapat mempersulit proses debug seperti disebutkan dalam jawaban sebelumnya. Jika saya menemukan bagian kode tertentu yang sulit di-debug, saya akan mematikan sementara optimizer, melakukan debugging untuk membuat kode berfungsi, lalu nyalakan kembali optimizer dan uji sekali lagi.

semaj
sumber
1
Tapi satu langkah kode hampir mustahil dengan optimisasi dihidupkan. Debug dengan optimisasi mati, dan jalankan tes unit Anda dengan kode rilis.
Rocketmagnet
3

Strategi normal saya adalah untuk mengembangkan dengan optimasi akhir (maks untuk ukuran atau kecepatan yang sesuai), tetapi matikan optimasi sementara jika saya perlu men-debug atau melacak sesuatu. Ini mengurangi risiko munculnya bug sebagai akibat dari perubahan tingkat optimisasi.

Mode kegagalan yang umum adalah ketika peningkatan optimasi menyebabkan bug yang sebelumnya tidak terlihat muncul ke permukaan karena Anda tidak memiliki variabel yang dinyatakan tidak stabil di mana diperlukan - ini penting untuk memberi tahu kompiler mana hal-hal yang tidak boleh 'dioptimalkan'.

mikeselectricstuff
sumber
2

Gunakan formulir apa pun yang akan Anda rilis, debugger dan kompilasi untuk debugging menyembunyikan banyak (BANYAK) bug yang tidak Anda lihat sampai Anda mengkompilasi untuk rilis. Pada saat itu jauh lebih sulit untuk menemukan bug-bug itu, dibandingkan dengan debugging saat Anda menggunakannya. 20 sesuatu tahun sekarang dan saya tidak pernah menggunakan untuk gdb atau debugger seperti lainnya, tidak perlu menonton variabel atau langkah tunggal. Ratusan hingga ribuan baris per hari. Jadi itu mungkin, jangan mengarah untuk berpikir sebaliknya.

Kompilasi untuk debug kemudian kompilasi untuk rilis dapat dan akan membutuhkan dua kali hingga lebih dari dua kali upaya. Jika Anda masuk ke dalam ikatan dan harus menggunakan alat seperti debugger kemudian kompilasi untuk debugger untuk mengatasi masalah tertentu, kemudian kembali ke operasi normal.

Masalah lain juga benar seperti pengoptimal membuat kode lebih cepat sehingga untuk tertanam khususnya perubahan waktu Anda dengan opsi kompiler dan yang dapat mempengaruhi fungsi program Anda, di sini lagi menggunakan pilihan kompilasi yang dapat disampaikan selama seluruh fase. Kompiler adalah program juga dan memiliki bug dan pengoptimal membuat kesalahan dan beberapa tidak memiliki kepercayaan pada itu. Jika itu masalahnya tidak ada yang salah dengan kompilasi tanpa optimasi, lakukan saja seperti itu setiap saat. Jalur yang saya sukai adalah mengkompilasi untuk optimasi lalu jika saya mencurigai masalah kompiler menonaktifkan optimasi jika perbaikan itu biasanya bolak-balik kadang-kadang memeriksa output assembler untuk mencari tahu mengapa.

old_timer
sumber
1
+1 hanya untuk menguraikan jawaban Anda yang baik: sering dikompilasi dalam mode "debug" akan mengisi variabel / tumpukan di sekitar variabel dengan ruang yang tidak dialokasikan untuk mengurangi kesalahan write-off-the-end yang kecil dan memformat kesalahan string. Anda akan lebih sering mendapatkan crash runtime yang baik jika Anda mengkompilasi dalam rilis.
Morten Jensen
2

Saya selalu mengembangkan kode dengan -O0 (opsi gcc untuk mematikan optimisasi). Ketika saya merasa saya pada titik di mana saya ingin mulai membiarkan hal-hal lebih mengarah ke rilis, saya akan mulai dengan -Os (optimalkan untuk ukuran) karena umumnya semakin banyak kode yang dapat Anda simpan dalam cache semakin baik, bahkan jika itu bukan super-duper yang dioptimalkan.

Saya menemukan bahwa gdb bekerja jauh lebih baik dengan kode -O0, dan itu jauh lebih mudah untuk diikuti jika Anda harus masuk ke majelis. Beralih antara -O0 dan -Os juga memungkinkan Anda melihat apa yang dilakukan kompiler terhadap kode Anda. Terkadang ini adalah pendidikan yang cukup menarik, dan juga dapat mengungkap bug kompiler ... hal-hal buruk yang membuat Anda menarik rambut Anda mencoba mencari tahu apa yang salah dengan kode Anda!

Jika saya benar-benar perlu, saya akan mulai menambahkan -fdata-bagian dan -fcode-bagian dengan --gc-bagian, yang memungkinkan penghubung untuk menghapus seluruh fungsi dan segmen data yang sebenarnya tidak digunakan. Ada banyak hal kecil yang dapat Anda mainkan untuk mencoba mengecilkan lebih jauh atau membuat segalanya lebih cepat, tetapi pada umumnya ini adalah satu-satunya trik yang akhirnya saya gunakan, dan apa pun yang harus lebih kecil atau lebih cepat saya akan menyerahkan -berkumpul.

akohlsmith
sumber
2

Ya, menonaktifkan pengoptimalan selama debugging telah menjadi praktik terbaik untuk sementara waktu sekarang, karena tiga alasan:

  • (a) jika Anda akan melakukan satu langkah program dengan debugger tingkat tinggi, itu sedikit kurang membingungkan.
  • (a) (usang) jika Anda akan men-debug program satu langkah dengan debugger bahasa assembly, itu jauh lebih membingungkan. (Tapi mengapa Anda repot-repot dengan ini ketika Anda bisa menggunakan debugger tingkat tinggi?)
  • (B) (lama usang) Anda mungkin hanya akan menjalankan executable khusus ini sekali, kemudian membuat beberapa perubahan dan mengkompilasi ulang. Ini buang-buang waktu seseorang untuk menunggu 10 menit tambahan sementara kompiler "mengoptimalkan" eksekusi khusus ini, ketika itu akan menghemat kurang dari 10 menit runtime. (Ini tidak lagi relevan dengan PC modern yang dapat mengkompilasi mikrokontroler yang dapat dieksekusi, dengan optimisasi penuh, dalam waktu kurang dari 2 detik).

Banyak orang bahkan melangkah lebih jauh ke arah ini, dan kapal dengan pernyataan diaktifkan .

davidcary
sumber
Satu langkah melalui kode perakitan dapat sangat berguna untuk mendiagnosis kasus-kasus di mana kode sumber sebenarnya menentukan sesuatu selain dari apa yang tampak seperti (misalnya "longvar1 & = ~ 0x40000000; longvar2 & = ~ 0x80000000;") atau di mana kompiler menghasilkan kode kereta . Saya telah melacak beberapa masalah menggunakan debugger kode mesin yang saya pikir saya tidak bisa melacaknya dengan cara lain.
supercat
2

Sederhana: optimasi memerlukan banyak waktu, dan mungkin tidak berguna jika Anda harus mengubah kode itu nanti dalam pengembangan. Jadi mereka mungkin membuang-buang waktu dan uang.
Mereka berguna untuk modul jadi, namun; bagian dari kode yang kemungkinan besar tidak perlu diubah lagi.

stevenvh
sumber
2

itu tentu masuk akal dalam kasus break point ... karena kompiler dapat menghapus banyak pernyataan yang tidak benar-benar mempengaruhi memori.

pertimbangkan sesuatu seperti:

int i =0;

for (int j=0; j < 10; j++)
{
 i+=j;
}
return 0;

dapat sepenuhnya dioptimalkan (karena itidak pernah dibaca). itu akan terlihat dari sudut pandang breakpoint Anda bahwa ia melompati semua kode itu, ketika pada dasarnya tidak ada di sana .... Saya kira itu sebabnya dalam fungsi tipe tidur Anda akan sering melihat sesuatu seperti:

for (int j=delay; j != 0; j--)
{
    asm( " nop " );
    asm( " nop " );
}
return 0;
Grady Player
sumber
1

Jika Anda menggunakan debugger maka saya akan menonaktifkan optimasi dan mengaktifkan debug.

Secara pribadi saya menemukan bahwa debugger PIC menyebabkan lebih banyak masalah daripada yang membantu saya memperbaiki.
Saya hanya menggunakan printf () untuk USART untuk men-debug program saya yang ditulis dalam C18.

mjh2007
sumber
1

Sebagian besar argumen yang menentang pengaktifan pengoptimalan dalam kompilasi Anda adalah:

  1. masalah dengan debugging (konektivitas JTAG, breakpoints dll)
  2. waktu perangkat lunak yang salah
  3. sh * t berhenti bekerja dengan benar

IMHO dua yang pertama adalah sah, yang ketiga tidak begitu banyak. Ini sering berarti Anda memiliki kode yang buruk atau mengandalkan eksploitasi bahasa / implementasi yang tidak aman atau mungkin penulis hanya penggemar perilaku Paman Tidak Terdefinisi yang baik.

Blog Embedded in Academia memiliki satu atau dua hal untuk dikatakan tentang perilaku yang tidak terdefinisi, dan posting ini adalah tentang bagaimana kompiler mengeksploitasinya: http://blog.regehr.org/archives/761

Morten Jensen
sumber
Alasan lain yang mungkin adalah bahwa kompiler mungkin sangat lambat ketika optimisasi dihidupkan.
supercat