Bagaimana cara memeras kode untuk lebih banyak Flash dan RAM? [Tutup]


14

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.


1
Terima kasih semuanya atas sarannya. Saya akan membuat Anda diperbarui dengan kemajuan saya dan daftar langkah-langkah yang berhasil, dan yang tidak.
IntelliChick

Ok jadi inilah hal-hal yang saya coba yang berhasil: Memindahkan versi kompiler. Optimalisasi telah meningkat secara drastis yang memberi saya sekitar 2K Flash. Pergi melalui daftar file untuk memeriksa fungsionalitas yang berlebihan dan tidak digunakan (diwarisi karena basis kode umum) untuk produk tertentu dan memperoleh lebih banyak Flash.
IntelliChick

Untuk RAM saya melakukan hal berikut: Pergi melalui file peta, untuk memeriksa fungsi / modul yang menggunakan sebagian besar RAM. Saya menemukan fungsi yang sangat berat (12 saluran, masing-masing dengan jumlah memori yang dialokasikan tetap), kode warisan, memahami apa yang ingin dicapai, dan mengoptimalkan penggunaan RAM, dengan berbagi informasi antara saluran yang umum. Ini memberi saya ~ 200 Bytes yang saya butuhkan.
IntelliChick

Jika Anda memiliki file ascii, Anda dapat menggunakan kompresi 8 hingga 7 bit. Menghemat 12,5%. Menggunakan file zip akan membutuhkan lebih banyak kode untuk zip dan un-zip daripada membiarkannya saja.
Sparky256

Jawaban:


18

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 printfs. Sebagai contoh masing printf- 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.


1
Terima kasih atas sarannya, saya pasti dapat mencoba sebagian besar dari mereka, kecuali satu untuk konstanta string. Ini murni perangkat tertanam, tanpa UI dan karenanya tidak ada panggilan untuk mencetak () dalam kode. Berharap bahwa saran-saran itu akan membujuk saya untuk mendapatkan 1400 Bytes Flash / 200 byte RAM yang saya butuhkan.
IntelliChick

1
@IntelliChick Anda akan kagum pada berapa banyak orang yang menggunakan printf () di dalam perangkat tertanam untuk mencetak baik untuk debugging atau mengirim ke perangkat. Sepertinya Anda tahu lebih baik dari ini, tetapi jika ada yang menulis kode pada proyek sebelum Anda, tidak ada salahnya untuk memeriksanya.
Kellenjb

5
Dan hanya untuk memperluas komentar saya sebelumnya, Anda juga akan kagum pada berapa banyak orang menambahkan pernyataan debugging, tetapi tidak pernah menghapusnya. Bahkan orang-orang yang melakukan #ifdefs terkadang masih malas.
Kellenjb

1
Keren terima kasih! Saya telah mewarisi basis kode ini, jadi sakit pasti harus mencari mereka. Saya akan membuat kalian diposting, pada kemajuan, dan mencoba untuk melacak berapa banyak byte memori atau Flash yang saya dapatkan dengan melakukan apa, hanya sebagai referensi untuk orang lain yang mungkin perlu melakukan ini di masa depan.
IntelliChick

Hanya pertanyaan tentang ini - bagaimana dengan panggilan fungsi bersarang melompat dari lapisan ke lapisan. Berapa banyak overhead yang ditambahkan? Apakah lebih baik menjaga modularitas dengan memiliki beberapa fungsi panggilan atau mengurangi fungsi panggilan, dan menyimpan beberapa byte. Dan apakah itu penting?
IntelliChick

8

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.


3
Sangat penting bagi Anda untuk mengetahui apa yang dilakukan kompiler Anda. Anda harus melihat apa yang ada di kompiler saya. Itu membuat bayi menangis (termasuk saya sendiri).
Kortuk

8

Optimalisasi kompiler, misalnya, -Osdalam GCC memberikan keseimbangan terbaik antara kecepatan dan ukuran kode. Hindari -O3, karena dapat meningkatkan ukuran kode.


3
Jika Anda melakukan ini, Anda perlu menguji kembali SEGALA SESUATU! Optimalisasi dapat menyebabkan kode kerja tidak berfungsi karena asumsi baru yang dibuat oleh kompiler.
Robert

@ Robert, itu hanya benar jika Anda menggunakan pernyataan yang tidak ditentukan: misalnya a = a ++ akan mengkompilasi berbeda di -O0 dan -O3.
Thomas O

5
@ Thomas tidak benar. Jika Anda memiliki loop untuk menunda siklus jam, banyak pengoptimal akan menyadari Anda tidak melakukan apa-apa di dalamnya dan menghapusnya. Ini hanya 1 contoh.
Kellenjb

1
@ Thomas O, Anda juga perlu memastikan bahwa Anda berhati-hati tentang definisi fungsi volatil. Pengoptimal akan meledakkan mereka yang berpikir mereka tahu C dengan baik tetapi tidak memahami kompleksitas operasi atom.
Kortuk

1
Semua poin bagus. Fungsi / variabel yang mudah menguap, menurut definisi, TIDAK harus dioptimalkan. Pengoptimal apa pun yang melakukan pengoptimalan seperti itu (termasuk waktu panggilan dan inlining) rusak.
Thomas O

8

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?


8

Jika ini adalah proyek lama tetapi kompiler telah dikembangkan sejak itu, bisa jadi kompiler yang lebih baru dapat menghasilkan kode yang lebih kecil


Terima kasih Mike! Saya telah mencoba ini di masa lalu, dan ruang yang diperoleh telah digunakan! :) Pindah dari kompiler IAR C 3.21d ke 3.40.
IntelliChick

1
Saya naik satu versi lagi, dan telah berhasil mendapatkan lebih banyak Flash agar sesuai dengan fitur. Saya benar-benar berjuang dengan RAM, yang tetap tidak berubah. :(
IntelliChick

7

Selalu ada baiknya memeriksa manual kompiler Anda untuk opsi untuk mengoptimalkan ruang.

Untuk gcc -ffunction-sections dan -fdata-sectionsdengan --gc-sectionsflag linker bagus untuk menghilangkan kode mati.

Berikut adalah beberapa tips hebat lainnya (diarahkan ke AVR)


Apakah ini berhasil? Dokumen mengatakan "Ketika Anda menentukan opsi ini, assembler dan linker akan membuat objek yang lebih besar dan file yang dapat dieksekusi dan juga akan lebih lambat." Saya mengerti bahwa memiliki bagian terpisah masuk akal untuk mikro dengan bagian Flash dan RAM - Apakah pernyataan ini dalam dokumen tidak berlaku untuk mikrokontroler?
Kevin Vermeer

Pengalaman saya adalah ini bekerja dengan baik untuk AVR
Toby Jaffey

1
Ini tidak berfungsi dengan baik di sebagian besar kompiler yang saya gunakan. Itu seperti menggunakan kata kunci register. Anda dapat memberi tahu kompiler bahwa suatu variabel masuk ke dalam register, tetapi pengoptimal yang baik akan melakukan ini jauh lebih baik daripada manusia (berdebat karena beberapa orang mungkin, dianggap tidak dapat diterima untuk melakukan ini dalam praktiknya).
Kortuk

1
Ketika Anda mulai menetapkan lokasi, Anda memaksa kompiler untuk menempatkan sesuatu di lokasi tertentu, sangat penting untuk kode boot-loader yang canggih, tetapi buruk untuk berurusan dengan pengoptimal, saat Anda membuat keputusan untuk itu, Anda mengambil langkah optimasi itu bisa melakukan. Dalam beberapa kompiler mereka mendesain untuk memiliki bagian untuk kode apa yang digunakan, ini adalah kasus memberitahu kompiler informasi lebih lanjut untuk memahami penggunaan Anda, ini akan membantu. Jika kompiler tidak menyarankannya, jangan lakukan itu.
Kortuk

6

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.


Ini adalah pilihan yang sangat bagus. Saya akan perhatikan bahwa Anda seharusnya tidak pernah menggunakan malloc dalam sistem embedded.
Kortuk

1
@Kortuk Itu tergantung pada definisi Anda tentang embedded dan tugas yang dilakukan
Toby Jaffey

1
@joby, Ya, saya mengerti itu. Dalam sistem dengan 0 restart dan tidak adanya OS seperti linux, Malloc bisa sangat sangat buruk.
Kortuk

Tidak ada alokasi memori dinamis, tidak ada tempat malloc, calloc digunakan. Saya juga sudah memeriksa alokasi heap, dan sudah diset ke 0, jadi tidak ada alokasi heap. Ukuran Stack yang dialokasikan saat ini adalah 254 Bytes dan ukuran stack interupsi dalam 128 byte.
IntelliChick

5

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.


Ada 2 tabel pencarian kecil, yang sayangnya tidak akan berjumlah 10 ribu. Dan tidak ada matematika floating point yang digunakan juga. :( Terima kasih atas sarannya. Itu bagus.
IntelliChick

2

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 .


2

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.


1
Saat ini dioptimalkan ke Maksimum untuk Ukuran. :) Terima kasih atas sarannya. :)
IntelliChick

2

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.


Terima kasih Mike. Kode sudah menggunakan serikat di sebagian besar tempat, tetapi saya akan melihat beberapa tempat lain, di mana ini mungkin masih membantu. Saya juga akan melihat ukuran tumpukan yang dipilih dan melihat apakah itu dapat dioptimalkan sama sekali.
IntelliChick

Bagaimana saya tahu ukuran ukuran Stack apa yang sesuai?
IntelliChick

2

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)


Terima kasih Mike. Ya saya sudah memeriksa itu - yang disebut opsi 'konstanta Writeable' untuk kompiler M16C IAR C. Ini menyalin konstanta dari ROM ke RAM. Opsi ini tidak dicentang untuk proyek saya. Tapi cek benar-benar valid! Terima kasih.
IntelliChick

1

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.


1

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


1

Beberapa trik (mungkin jelas) yang saya gunakan dengan sukses dalam mengompresi beberapa kode pelanggan:

  1. 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.

  2. Cari redundansi dalam kode, dan gunakan loop atau fungsi untuk menjalankan pernyataan berulang.

  3. 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 array


0

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.


1
Setiap kompiler yang layak harus melakukan ini secara otomatis untuk fungsi yang dipanggil hanya sekali.
mikeselectricstuff

5
Saya menemukan inlining biasanya lebih berguna untuk optimisasi kecepatan, dan biasanya dengan biaya peningkatan ukuran.
Craig McQueen

inlining biasanya akan meningkatkan ukuran kode, kecuali dengan fungsi sepele sepertiint get_a(struct x) {return x.a;}
Dmitry Grigoryev

0

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.

for( i = 0; i < 100; i++ )

Ini mungkin tampak seperti tempat yang baik untuk variabel 8-bit, tetapi variabel 32-bit mungkin menghasilkan lebih sedikit kode.


Itu mungkin menghemat kode tetapi itu akan memakan RAM.
mikeselectricstuff

Ini hanya akan memakan RAM jika panggilan fungsi itu berada di cabang terpanjang dari jejak panggilan. Jika tidak, itu menggunakan kembali ruang stack yang beberapa fungsi lain sudah butuhkan.
Robert

2
Biasanya benar jika variabel lokal. Jika kekurangan RAM, ukuran vars global, terutama array, adalah tempat yang baik untuk mulai mencari penghematan.
mikeselectricstuff

1
Kemungkinan lain, yang menarik, adalah mengganti variabel yang tidak ditandatangani dengan yang ditandatangani. Jika sebuah kompiler mengoptimalkan suatu unsigned short ke register 32-bit, ia harus menambahkan kode untuk memastikan bahwa nilainya membungkus dari 65535 menjadi nol. Namun, jika kompiler mengoptimalkan pendek yang ditandatangani untuk register, tidak ada kode yang diperlukan. Karena tidak ada jaminan apa yang akan terjadi jika kekurangan meningkat di atas 32767, kompiler tidak diharuskan untuk memancarkan kode untuk menghadapinya. Pada setidaknya dua kompiler ARM yang pernah saya lihat, kode pendek-bertanda dapat lebih kecil dari kode singkat-unsigned untuk alasan itu.
supercat
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.