C Integer Promotion pada MCU 8-bit

14

Menggunakan avr-gcc sebagai contoh, tipe int ditentukan selebar 16-bit. Melakukan operasi pada operan 8-bit dalam C menghasilkan operan tersebut dikonversi ke tipe int 16-bit karena promosi integer dalam C. Apakah ini berarti bahwa semua operasi aritmatika 8-bit pada AVR akan memakan waktu lebih lama jika ditulis dalam C daripada jika ditulis dalam rakitan karena promosi bilangan bulat C?

pr871
sumber
1
Saya tidak berpikir begitu, kompiler akan menyadari bahwa variabel tujuan adalah char (unsigned), oleh karena itu tidak akan repot dalam menghitung 8 bit teratas. Namun, saya menemukan bahwa GCC kadang-kadang tidak begitu baik dalam mengoptimalkan kode, oleh karena itu jika Anda kode dalam ASM, hasilnya MGIHT lebih cepat. Namun, kecuali jika Anda melakukan tugas / interrutps yang sangat kritis terhadap waktu, dengan kendala anggaran yang sangat kuat, maka Anda harus memilih prosesor yang lebih kuat dan memprogramnya dalam C, atau hanya tidak khawatir tentang kinerja yang lebih rendah (pertimbangkan waktu sebagai gantinya -untuk dipasarkan, pembacaan / reusabilitas kode yang lebih baik, lebih sedikit bug, ecc ..).
next-hack
Saya minta maaf karena tidak punya waktu untuk memeriksa. Namun, saya pikir ada flag command-line ke gcc yang akan mengontrol 'integer promotion'. Bahkan mungkin ada pragma untuk mengontrolnya untuk potongan kode tertentu. Seberapa kritis kinerja? Dalam banyak penggunaan AVR, perbedaan kecepatan untuk beberapa aritmatika tidak menjadi masalah. Foxus mendapatkan kode berfungsi dengan benar terlebih dahulu. Lalu jika ada masalah kinerja, cari tahu apa itu. Akan mudah untuk membuang waktu pengkodean dalam assembler, hanya kamu yang menemukan itu tidak masalah.
gbulmer
1
hanya membongkar dan melihat apa yang dilakukan kompiler. Dari perspektif bahasa murni ya. implementasi di sini tidak biasa. biasanya int mencoba menyelaraskan diri dengan ukuran register, dan jika Anda memiliki register 16 bit maka matematika 8 bit sebenarnya lebih murah pada 16 bit daripada 8. Tetapi ini adalah sebaliknya dan dengan 8 bit mcu masuk akal untuk mengimplementasikan sebagai 16 bit. jadi Anda mungkin harus menggunakan uchar di mana Anda peduli tentang hal ini tetapi jangan menjadikannya kebiasaan pemrograman yang umum karena hal itu paling menyakitkan bagi Anda di tempat lain.
old_timer
3
Ingat: Hindari menjawab pertanyaan dalam komentar.
pipa
4
Pertanyaan semacam ini lebih baik ditanyakan kepada pakar C di SO, karena ini adalah pertanyaan perangkat lunak murni. Promosi integer dalam C adalah topik yang agak rumit - rata-rata programmer C akan memiliki banyak kesalahpahaman tentang itu.
Lundin

Jawaban:

16

Singkat cerita:

Promosi integer ke 16 bit selalu terjadi - standar C memberlakukan ini. Tetapi kompiler diperbolehkan untuk mengoptimalkan perhitungan kembali ke 8 bit (kompiler sistem embedded biasanya cukup bagus dalam optimasi seperti itu), jika dapat disimpulkan bahwa tanda akan sama dengan yang seharusnya jika jenis telah dipromosikan.

Ini tidak selalu terjadi! Perubahan penandatanganan implisit yang disebabkan oleh promosi integer adalah sumber bug yang umum dalam sistem embedded.

Penjelasan terperinci dapat ditemukan di sini: Aturan promosi tipe implisit .

Lundin
sumber
8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

seperti yang diharapkan fun1 adalah semua ints begitu juga matematika 16 bit

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

Meskipun secara teknis salah karena merupakan tambahan 16 bit yang dipanggil oleh kode, bahkan tidak dioptimalkan kompiler ini menghapus adc karena ukuran hasil.

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

tidak benar-benar terkejut di sini promosi terjadi, kompiler tidak terbiasa melakukan ini tidak yakin versi apa yang membuat ini mulai terjadi, berlari ke awal ini dalam karir saya dan meskipun kompiler mempromosikan rusak (seperti di atas), melakukan promosi meskipun saya menyuruhnya melakukan matematika uchar, tidak terkejut.

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

dan yang ideal, saya tahu itu 8 bit, ingin hasil 8 bit jadi saya hanya menyuruhnya melakukan 8 bit sepanjang jalan.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Jadi secara umum lebih baik untuk bertujuan untuk ukuran register, yang idealnya ukuran int (u), untuk 8 bit mcu seperti ini penulis kompiler harus membuat kompromi ... Intinya jangan membuat kebiasaan menggunakan uchar untuk matematika yang Anda tahu tidak perlu lebih dari 8 bit seperti ketika Anda memindahkan kode itu atau menulis kode baru seperti itu pada prosesor dengan register yang lebih besar sekarang kompiler harus mulai menutupi dan menandatangani perluasan, yang beberapa lakukan secara asli dalam beberapa instruksi, dan yang lainnya tidak.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

memaksa 8 bit lebih mahal. Saya curang sedikit / banyak, akan membutuhkan contoh yang sedikit lebih rumit untuk melihat lebih banyak tentang ini secara adil.

EDIT berdasarkan diskusi komentar

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

tidak ada kejutan. Meskipun mengapa pengoptimal meninggalkan instruksi tambahan itu, bisakah Anda tidak menggunakan ldi pada r19? (Saya tahu jawabannya ketika saya menanyakannya).

EDIT2

untuk avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

untuk menghindari kebiasaan buruk atau tidak perbandingan 8 bit

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

jelas optimisasi hanya membutuhkan waktu satu detik untuk mencoba dengan kompiler Anda sendiri untuk melihat bagaimana perbandingannya dengan output saya, tetapi bagaimanapun:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

Dan ya menggunakan byte untuk variabel ukuran byte, tentu saja pada avr, pic, dll, akan menghemat memori Anda dan Anda ingin benar-benar mencoba untuk melestarikannya ... jika Anda benar-benar menggunakannya, tetapi seperti yang ditunjukkan di sini sesedikit mungkin adalah akan berada dalam memori, sebanyak mungkin dalam register, sehingga penghematan flash datang dengan tidak memiliki variabel tambahan, penghematan ram mungkin atau mungkin tidak nyata ..

old_timer
sumber
2
"Kompiler tidak terbiasa melakukan ini tidak yakin versi apa yang membuat ini mulai terjadi, berlari ke awal ini dalam karir saya dan meskipun kompiler mempromosikan rusak (seperti di atas), melakukan promosi meskipun saya menyuruhnya melakukan uchar matematika, tidak terkejut. " Hal ini karena sistem embedded C compiler yang digunakan untuk memiliki kesesuaian standar mengerikan :) Compiler biasanya diperbolehkan untuk mengoptimalkan, tapi di sini tidak dapat mengurangi bahwa hasilnya akan cocok dalam unsigned charsehingga memiliki untuk melakukan promosi untuk 16 bit, seperti yang diperlukan menurut standar.
Lundin
1
@old_timer (a<<8)|bselalu salah untuk sistem mana pun yang intmemiliki 16 bit. aakan dipromosikan secara implisit intyang ditandatangani. Dalam kasus amemegang nilai dalam MSB, Anda akhirnya menggeser data itu ke bit tanda nomor 16 bit, yang memanggil perilaku tidak terdefinisi.
Lundin
1
fun3 is fun..ny ... benar-benar tidak dioptimalkan oleh kompiler ... Dianggap bahwa r1 selalu 0 di GCC, dan menunjukkan ra, rb, {rh, rl} register untuk variabel a, b, dan hasilnya, kompiler dapat melakukan: 1) mov rh, r1; 2) mov rl, ra; 2) tambahkan rl, rb; 3) adc rh, rh; 4) ret. 4 Instruksi, vs 7 atau 8 ... Instruksi 1 dapat diubah di ldi rh, 0.
next-hack
1
Ini akan menjadi jawaban yang lebih baik jika itu menentukan kompiler dan opsi yang relevan digunakan.
Russell Borogove
1
Ini adalah ide yang baik untuk menghindari penggunaan int / char dll dan alih-alih menggunakan int16_t dan int8_t yang lebih eksplisit dan mudah dibaca.
pengguna
7

Tidak harus, karena kompiler modern melakukan pekerjaan yang baik dalam mengoptimalkan kode yang dihasilkan. Misalnya, jika Anda menulis di z = x + y;mana semua variabel berada unsigned char, kompiler harus mempromosikannya unsigned intsebelum melakukan perhitungan. Namun, karena hasil akhirnya akan sama persis tanpa promosi, kompiler akan menghasilkan kode yang hanya menambahkan variabel 8-bit.

Tentu saja, ini tidak selalu terjadi, misalnya hasil z = (x + y)/2;tergantung pada byte atas, sehingga promosi akan berlangsung. Itu masih bisa dihindari tanpa menggunakan perakitan dengan mengembalikan hasil antara unsigned char.

Beberapa inefisiensi semacam itu dapat dihindari dengan menggunakan opsi kompiler. Sebagai contoh, banyak kompiler 8-bit memiliki pragma atau saklar baris perintah agar sesuai dengan tipe enumerasi dalam 1 byte, alih-alih intseperti yang disyaratkan oleh C.

Dmitry Grigoryev
sumber
4
"kompiler diperlukan untuk mempromosikannya ke int yang tidak ditandatangani". Tidak, kompiler harus mempromosikannya int, karena charkemungkinan besar tidak memiliki peringkat konversi yang sama seperti intpada platform apa pun.
Lundin
3
"Sebagai contoh, banyak kompiler 8-bit memiliki pragma atau saklar baris perintah agar sesuai dengan tipe enumerasi dalam 1 byte, bukan int seperti yang disyaratkan oleh C." Standar C memungkinkan variabel enumerasi dialokasikan dalam 1 byte. Ini hanya mensyaratkan bahwa konstanta enumerasi harus int(ya tidak konsisten). C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin