Dalam banyak kasus, cara optimal untuk melakukan beberapa tugas mungkin tergantung pada konteks di mana tugas itu dilakukan. Jika suatu rutin ditulis dalam bahasa assembly, secara umum tidak mungkin urutan instruksi bervariasi berdasarkan konteks. Sebagai contoh sederhana, pertimbangkan metode sederhana berikut:
inline void set_port_high(void)
{
(*((volatile unsigned char*)0x40001204) = 0xFF);
}
Kompiler untuk kode ARM 32-bit, yang diberikan di atas, kemungkinan akan membuatnya sebagai sesuatu seperti:
ldr r0,=0x40001204
mov r1,#0
strb r1,[r0]
[a fourth word somewhere holding the constant 0x40001204]
atau mungkin
ldr r0,=0x40001000 ; Some assemblers like to round pointer loads to multiples of 4096
mov r1,#0
strb r1,[r0+0x204]
[a fourth word somewhere holding the constant 0x40001000]
Itu bisa dioptimalkan sedikit dalam kode rakitan tangan, seperti:
ldr r0,=0x400011FF
strb r0,[r0+5]
[a third word somewhere holding the constant 0x400011FF]
atau
mvn r0,#0xC0 ; Load with 0x3FFFFFFF
add r0,r0,#0x1200 ; Add 0x1200, yielding 0x400011FF
strb r0,[r0+5]
Kedua pendekatan rakitan tangan akan membutuhkan 12 byte ruang kode daripada 16; yang terakhir akan menggantikan "load" dengan "add", yang pada ARM7-TDMI akan mengeksekusi dua siklus lebih cepat. Jika kode akan dieksekusi dalam konteks di mana r0 tidak tahu / tidak peduli, maka versi bahasa assembly akan lebih baik daripada versi yang dikompilasi. Di sisi lain, anggap kompiler tahu bahwa beberapa register [misalnya r5] akan menyimpan nilai yang berada dalam 2047 byte dari alamat yang diinginkan 0x40001204 [misalnya 0x40001000], dan selanjutnya mengetahui bahwa beberapa register lain [misalnya r7] akan untuk memegang nilai yang bit-bit rendahnya adalah 0xFF. Dalam hal ini, kompiler dapat mengoptimalkan versi kode C hanya untuk:
strb r7,[r5+0x204]
Jauh lebih pendek dan lebih cepat daripada kode perakitan yang dioptimalkan dengan tangan. Selanjutnya, anggap set_port_high terjadi dalam konteks:
int temp = function1();
set_port_high();
function2(temp); // Assume temp is not used after this
Sama sekali tidak masuk akal ketika coding untuk sistem tertanam. Jika set_port_high
ditulis dalam kode assembly, kompiler harus memindahkan r0 (yang menyimpan nilai balik function1
) dari tempat lain sebelum memanggil kode assembly, dan kemudian memindahkan nilai itu kembali ke r0 sesudahnya (karena dengan satu instruksi empat lebih kecil dan lebih cepat daripada kode perakitan "dioptimalkan dengan tangan".function2
akan mengharapkan parameter pertama di r0), jadi kode perakitan "yang dioptimalkan" akan membutuhkan lima instruksi. Bahkan jika kompiler tidak mengetahui register yang menyimpan alamat atau nilai untuk menyimpan, versi empat instruksi (yang dapat diadaptasi untuk menggunakan register yang tersedia - tidak harus r0 dan r1) akan mengalahkan rakitan "dioptimalkan" Versi bahasa. Jika kompiler memiliki alamat dan data yang diperlukan dalam r5 dan r7 seperti yang dijelaskan sebelumnya, function1
tidak akan mengubah register tersebut, dan dengan demikian ia dapat menggantikanset_port_high
strb
instruksi--
Perhatikan bahwa kode rakitan yang dioptimalkan dengan tangan sering kali dapat mengungguli kompiler dalam kasus-kasus di mana programmer mengetahui aliran program yang tepat, tetapi kompiler bersinar dalam kasus-kasus di mana sepotong kode ditulis sebelum konteksnya diketahui, atau di mana satu bagian dari kode sumber mungkin dipanggil dari berbagai konteks [jika set_port_high
digunakan di lima puluh tempat yang berbeda dalam kode, kompilator dapat memutuskan sendiri untuk masing-masing cara terbaik untuk mengembangkannya].
Secara umum, saya akan menyarankan bahwa bahasa assembly cenderung untuk menghasilkan peningkatan kinerja terbesar dalam kasus-kasus di mana setiap bagian kode dapat didekati dari sejumlah konteks yang sangat terbatas, dan cenderung merusak kinerja di tempat-tempat di mana sepotong kode dapat didekati dari banyak konteks yang berbeda. Menariknya (dan mudahnya) kasus-kasus di mana perakitan paling bermanfaat bagi kinerja sering kali adalah di mana kode paling mudah dan mudah dibaca. Tempat-tempat kode bahasa majelis akan berubah menjadi berantakan lengket sering kali tempat menulis dalam pertemuan akan menawarkan manfaat kinerja terkecil.
[Catatan kecil: ada beberapa tempat kode perakitan dapat digunakan untuk menghasilkan kekacauan lengket yang dioptimalkan; misalnya, sepotong kode yang saya lakukan untuk ARM diperlukan untuk mengambil kata dari RAM dan menjalankan salah satu dari sekitar dua belas rutinitas berdasarkan enam bit teratas dari nilai (banyak nilai dipetakan ke rutin yang sama). Saya rasa saya mengoptimalkan kode itu ke sesuatu seperti:
ldrh r0,[r1],#2! ; Fetch with post-increment
ldrb r1,[r8,r0 asr #10]
sub pc,r8,r1,asl #2
Register r8 selalu menyimpan alamat tabel pengiriman utama (dalam loop di mana kode menghabiskan 98% waktunya, tidak ada yang pernah menggunakannya untuk tujuan lain); semua 64 entri merujuk ke alamat dalam 256 byte sebelumnya. Karena loop utama dalam kebanyakan kasus memiliki batas waktu eksekusi yang sulit sekitar 60 siklus, pengambilan dan pengiriman sembilan siklus sangat berperan dalam mencapai tujuan itu. Menggunakan tabel 256 alamat 32-bit akan menjadi satu siklus lebih cepat, tetapi akan menelan 1KB RAM yang sangat berharga [flash akan menambahkan lebih dari satu keadaan tunggu]. Menggunakan 64 alamat 32-bit akan membutuhkan penambahan instruksi untuk menutupi beberapa bit dari kata yang diambil, dan masih akan menelan 192 byte lebih banyak daripada tabel yang sebenarnya saya gunakan. Menggunakan tabel offset 8-bit menghasilkan kode yang sangat ringkas dan cepat, tapi bukan sesuatu yang saya harapkan akan dikompilasi oleh kompiler; Saya juga tidak akan mengharapkan kompiler untuk mendedikasikan register "penuh waktu" untuk memegang alamat tabel.
Kode di atas dirancang untuk berjalan sebagai sistem mandiri; secara berkala dapat memanggil kode C, tetapi hanya pada waktu-waktu tertentu ketika perangkat keras yang digunakan berkomunikasi dengan aman dapat dimasukkan ke dalam status "idle" selama dua interval kira-kira satu milidetik setiap 16ms.