Saya telah bekerja mengembangkan fitur pada produk kami. Telah ada permintaan untuk port fitur yang sama ke produk lain. Produk ini didasarkan pada mikrokontroler M16C, yang secara tradisional memiliki 64K Flash dan 2k RAM.
Ini adalah produk yang matang, dan oleh karena itu, hanya memiliki 132 Bytes Flash dan 2 Bytes RAM yang tersisa.
Untuk port fitur yang diminta (fitur itu sendiri telah dioptimalkan), saya perlu 1400 byte Flash dan ~ 200 Bytes RAM.
Adakah yang punya saran tentang cara mengambil Bytes ini dengan pemadatan kode? Apa hal spesifik yang saya cari ketika saya mencoba untuk memadatkan kode kerja yang sudah ada?
Setiap ide akan sangat dihargai.
Terima kasih.
Jawaban:
Anda memiliki beberapa opsi: pertama adalah mencari kode yang berlebihan dan memindahkannya ke satu panggilan untuk menghilangkan duplikasi; yang kedua adalah menghapus fungsionalitas.
Perhatikan baik-baik file .map Anda dan lihat apakah ada fungsi yang bisa Anda singkirkan atau tulis ulang. Pastikan juga bahwa panggilan perpustakaan yang sedang digunakan benar-benar diperlukan.
Hal-hal tertentu seperti pembagian dan perkalian dapat membawa banyak kode tetapi menggunakan shift dan penggunaan konstanta yang lebih baik dapat membuat kode lebih kecil. Juga lihat hal-hal seperti konstanta string dan
printf
s. Sebagai contoh masingprintf
- masing akan memakan rom Anda tetapi Anda mungkin dapat memiliki beberapa string format bersama alih-alih mengulangi string yang konstan berulang-ulang.Untuk memori lihat apakah Anda dapat menyingkirkan global dan menggunakan autos dalam suatu fungsi sebagai gantinya. Juga hindari variabel dalam fungsi utama sebanyak mungkin, karena ini memakan memori seperti halnya global.
sumber
Selalu bernilai melihat output file daftar (assembler) untuk mencari hal-hal yang sangat buruk dikompilasi oleh kompiler Anda.
Sebagai contoh, Anda mungkin menemukan bahwa variabel lokal sangat mahal, dan jika aplikasi cukup sederhana untuk sebanding dengan risikonya, memindahkan beberapa loop counter ke variabel statis mungkin menyimpan banyak kode.
Atau pengindeksan array mungkin sangat mahal tetapi operasi penunjuk jauh lebih murah. Atau sebaliknya.
Tetapi melihat bahasa majelis adalah langkah pertama.
sumber
Optimalisasi kompiler, misalnya,
-Os
dalam GCC memberikan keseimbangan terbaik antara kecepatan dan ukuran kode. Hindari-O3
, karena dapat meningkatkan ukuran kode.sumber
Untuk RAM, periksa rentang semua variabel Anda - apakah Anda menggunakan int di mana Anda bisa menggunakan char? Apakah buffer lebih besar dari yang seharusnya?
Pemerasan kode sangat bergantung pada aplikasi dan gaya pengkodean. Jumlah Anda yang tersisa menunjukkan bahwa mungkin kodenya telah hilang meskipun ada sedikit tekanan, yang berarti masih ada sedikit yang bisa didapat.
Perhatikan baik-baik keseluruhan fungsi - apakah ada sesuatu yang tidak benar-benar digunakan dan dapat dibuang?
sumber
Jika ini adalah proyek lama tetapi kompiler telah dikembangkan sejak itu, bisa jadi kompiler yang lebih baru dapat menghasilkan kode yang lebih kecil
sumber
Selalu ada baiknya memeriksa manual kompiler Anda untuk opsi untuk mengoptimalkan ruang.
Untuk gcc
-ffunction-sections
dan-fdata-sections
dengan--gc-sections
flag linker bagus untuk menghilangkan kode mati.Berikut adalah beberapa tips hebat lainnya (diarahkan ke AVR)
sumber
Anda dapat memeriksa jumlah ruang tumpukan dan ruang tumpukan yang dialokasikan. Anda mungkin bisa mendapatkan kembali jumlah RAM yang besar jika salah satu atau keduanya dialokasikan secara berlebihan.
Dugaan saya adalah untuk sebuah proyek yang cocok ke dalam 2k RAM untuk memulai dengan tidak ada alokasi memori dinamis (penggunaan
malloc
,calloc
, dll). Jika demikian, Anda dapat menyingkirkan tumpukan Anda dengan anggapan penulis asli meninggalkan sejumlah RAM yang dialokasikan untuk tumpukan itu.Anda harus sangat berhati-hati mengurangi ukuran tumpukan karena ini dapat menyebabkan bug yang sangat sulit ditemukan. Mungkin bermanfaat untuk memulai dengan menginisialisasi seluruh ruang stack ke nilai yang diketahui (sesuatu selain 0x00 atau 0xff karena nilai-nilai ini sudah umum terjadi) kemudian jalankan sistem untuk sementara waktu untuk melihat berapa banyak ruang stack yang tidak digunakan.
sumber
Apakah kode Anda menggunakan matematika floating point? Anda mungkin dapat menerapkan kembali algoritme Anda hanya dengan menggunakan integer matematika, dan menghilangkan biaya overhead menggunakan pustaka floating point C. Misalnya dalam beberapa aplikasi, fungsi-fungsi seperti sinus, log, exp dapat digantikan oleh aproksimasi polinomial integer.
Apakah kode Anda menggunakan tabel pencarian besar untuk algoritma apa pun, seperti perhitungan CRC? Anda dapat mencoba mengganti versi lain dari algoritma yang menghitung nilai saat itu juga, alih-alih menggunakan tabel pencarian. Peringatannya adalah bahwa algoritma yang lebih kecil kemungkinan besar lebih lambat, jadi pastikan Anda memiliki siklus CPU yang cukup.
Apakah kode Anda memiliki sejumlah besar data konstan, seperti tabel string, halaman HTML, atau grafik piksel (ikon)? Jika itu cukup besar (katakanlah 10 kB), mungkin ada baiknya menerapkan skema kompresi yang sangat sederhana untuk mengecilkan data dan mendekompresnya saat dibutuhkan.
sumber
Anda dapat mencoba mengatur ulang banyak kode, ke gaya yang lebih kompak. Tergantung pada apa yang dilakukan kode. Kuncinya adalah menemukan hal-hal yang serupa dan mengimplementasikannya kembali dalam hal satu sama lain. Ekstrem akan menggunakan bahasa tingkat yang lebih tinggi, seperti Forth, yang dengannya dapat lebih mudah untuk mencapai kepadatan kode yang lebih tinggi daripada di C atau assembler.
Berikut adalah Keempat untuk M16C .
sumber
Tetapkan tingkat optimisasi kompiler. Banyak IDE memiliki pengaturan yang memungkinkan untuk optimasi ukuran kode dengan mengorbankan waktu kompilasi (atau bahkan mungkin waktu pemrosesan dalam beberapa kasus). Mereka dapat menyelesaikan pemadatan kode dengan menjalankan kembali pengoptimal mereka beberapa kali, mencari pola pengoptimalan yang kurang umum, dan sejumlah trik lain yang mungkin tidak diperlukan untuk kompilasi kasual / debug. Biasanya, secara default, kompiler diatur ke tingkat optimisasi menengah. Gali di dalam pengaturan dan Anda harus dapat menemukan beberapa skala optimasi berbasis integer.
sumber
Jika Anda sudah menggunakan kompiler tingkat profesional seperti IAR, saya pikir Anda akan berjuang untuk mendapatkan penghematan serius dengan sedikit tweaker kode tingkat rendah - Anda harus mencari lebih ke arah menghapus fungsionalitas atau melakukan major menulis ulang bagian dengan cara yang lebih efisien. Anda harus menjadi pembuat kode yang lebih pintar daripada siapa pun yang menulis versi aslinya ... Adapun untuk RAM, Anda harus memperhatikan dengan seksama cara penggunaannya saat ini, dan melihat apakah ada ruang untuk overlay penggunaan RAM yang sama untuk hal yang berbeda pada waktu yang berbeda (serikat pekerja berguna untuk ini). Ukuran tumpukan dan tumpukan standar IAR di ARM / AVR yang saya cenderung terlalu murah hati, jadi ini adalah hal pertama yang harus dilihat.
sumber
Hal lain yang perlu diperiksa - beberapa kompiler pada beberapa arsitektur menyalin konstanta ke RAM - biasanya digunakan ketika akses ke konstanta flash lambat / sulit (mis. AVR) mis. Kompiler AVR IAR memerlukan kualifikasi _ _flash untuk tidak menyalin konstanta ke RAM)
sumber
Jika prosesor Anda tidak memiliki dukungan perangkat keras untuk parameter / stack lokal tetapi kompiler mencoba mengimplementasikan stack parameter run-time, dan jika kode Anda tidak perlu masuk kembali, Anda mungkin dapat menyimpan kode ruang dengan mengalokasikan variabel otomatis secara statis. Dalam beberapa kasus, ini harus dilakukan secara manual; dalam kasus lain, arahan kompiler dapat melakukannya. Alokasi manual yang efisien akan membutuhkan pembagian variabel antar rutinitas. Pembagian seperti itu harus dilakukan dengan hati-hati, untuk memastikan bahwa tidak ada rutin yang menggunakan variabel yang dianggap rutin lain sebagai "dalam lingkup", tetapi dalam beberapa kasus manfaat ukuran kode mungkin signifikan.
Beberapa prosesor memiliki konvensi pemanggilan yang dapat membuat beberapa gaya parameter-passing lebih efisien daripada yang lain. Sebagai contoh, pada pengontrol PIC18, jika sebuah rutin mengambil parameter satu byte tunggal, itu dapat diteruskan dalam register; jika dibutuhkan lebih dari itu, semuanya parameter harus dilewatkan dalam RAM. Jika suatu rutin akan mengambil dua parameter satu byte, mungkin yang paling efisien untuk "melewatkan" satu dalam variabel global, dan kemudian meneruskan yang lain sebagai parameter. Dengan rutinitas yang banyak digunakan, penghematan dapat bertambah. Mereka bisa sangat signifikan jika parameter yang dikirimkan melalui global adalah flag bit tunggal, atau jika biasanya akan memiliki nilai 0 atau 255 (karena ada instruksi khusus untuk menyimpan 0 atau 255 ke dalam RAM).
Pada ARM, menempatkan variabel global yang sering digunakan bersama dalam suatu struktur dapat secara signifikan mengurangi ukuran kode dan meningkatkan kinerja. Jika A, B, C, D, dan E adalah variabel global yang terpisah, maka kode yang menggunakan semuanya harus memuat alamat masing-masing ke dalam register; jika tidak ada cukup register, mungkin perlu memuat ulang alamat itu beberapa kali. Sebaliknya, jika mereka adalah bagian dari struktur global MyStuff yang sama, maka kode yang menggunakan MyStuff.A, MyStuff.B, dll. Dapat dengan mudah memuat alamat MyStuff sekali. Kemenangan Besar.
sumber
1.Jika kode Anda bergantung pada banyak struktur, pastikan anggota struktur dipesan dari yang paling banyak menggunakan memori.
Mis: "uint32_t uint16_t uint8_t" alih-alih "uint16_t uint8_t uint32_t"
Ini akan memastikan padding struktur minimum.
2.Gunakan const untuk variabel yang berlaku. Ini akan memastikan bahwa variabel-variabel itu akan berada di ROM dan tidak memakan RAM
sumber
Beberapa trik (mungkin jelas) yang saya gunakan dengan sukses dalam mengompresi beberapa kode pelanggan:
Memadatkan bendera menjadi bidang bit atau topeng bit. Ini mungkin bermanfaat karena biasanya boolean disimpan sebagai bilangan bulat sehingga membuang-buang memori. Ini akan menghemat RAM dan ROM dan biasanya tidak dilakukan oleh kompiler.
Cari redundansi dalam kode, dan gunakan loop atau fungsi untuk menjalankan pernyataan berulang.
Saya juga menyimpan beberapa ROM dengan mengganti banyak
if(x==enum_entry) <assignment>
pernyataan dari konstanta dengan array yang diindeks, dengan menjaga agar entri enum dapat digunakan sebagai indeks arraysumber
Jika Anda bisa, gunakan fungsi sebaris atau kompiler makro alih-alih fungsi kecil. Ada ukuran dan kecepatan overhead dengan melewati argumen dan sedemikian rupa sehingga dapat diatasi dengan membuat fungsi inline.
sumber
int get_a(struct x) {return x.a;}
Ubah variabel lokal menjadi ukuran yang sama dengan register CPU Anda.
Jika CPU 32-bit, gunakan variabel 32-bit meskipun nilai maks tidak akan pernah mencapai di atas 255. Jika Anda menggunakan variabel 8-bit, kompiler akan menambahkan kode untuk menutupi 24 bit teratas.
Tempat pertama yang akan saya lihat adalah variabel for-loop.
Ini mungkin tampak seperti tempat yang baik untuk variabel 8-bit, tetapi variabel 32-bit mungkin menghasilkan lebih sedikit kode.
sumber