Saya menggunakan int
tipe untuk menyimpan nilai. Dengan semantik program, nilainya selalu bervariasi dalam kisaran yang sangat kecil (0 - 36), dan int
(bukan a char
) digunakan hanya karena efisiensi CPU.
Sepertinya banyak optimasi aritmatika khusus dapat dilakukan pada sejumlah kecil bilangan bulat. Banyak pemanggilan fungsi pada bilangan bulat itu mungkin dioptimalkan menjadi satu set kecil operasi "ajaib", dan beberapa fungsi bahkan mungkin dioptimalkan ke dalam pencarian tabel.
Jadi, apakah mungkin untuk memberitahu kompiler bahwa ini int
selalu dalam kisaran kecil, dan apakah mungkin bagi kompiler untuk melakukan optimasi tersebut?
unsigned
jenis karena mereka lebih mudah untuk dikompilasi dengan kompilator.var value: 0..36;
.int
danunsigned int
perlu sign-atau zero-extended dari 32 ke 64-bit, juga, pada kebanyakan sistem dengan pointer 64-bit. Perhatikan bahwa pada x86-64, operasi pada register 32-bit nol-rentangkan ke 64-bit gratis (bukan perpanjangan tanda, tetapi overflow yang ditandatangani adalah perilaku yang tidak terdefinisi, sehingga kompiler dapat menggunakan matematika bertanda-tangan 64-bit jika diinginkan). Jadi, Anda hanya melihat instruksi tambahan untuk memperpanjang argumen fungsi 32-bit, bukan hasil perhitungan. Anda akan melakukannya untuk tipe unsigned yang lebih sempit.Jawaban:
Ya itu mungkin. Misalnya, untuk
gcc
Anda dapat menggunakan__builtin_unreachable
untuk memberi tahu kompiler tentang kondisi yang tidak mungkin, seperti:Kami dapat membungkus kondisi di atas dalam makro:
Dan gunakan seperti ini:
Seperti yang Anda lihat ,
gcc
lakukan optimasi berdasarkan informasi ini:Menghasilkan:
Namun satu kelemahannya adalah bahwa jika kode Anda pernah melanggar asumsi seperti itu, Anda mendapatkan perilaku yang tidak jelas .
Itu tidak memberi tahu Anda ketika ini terjadi, bahkan di build debug. Untuk men-debug / menguji / menangkap bug dengan asumsi lebih mudah, Anda dapat menggunakan makro asumsi / klaim hybrid (kredit ke @ David Z), seperti ini:
Dalam membangun debug (dengan
NDEBUG
tidak didefinisikan), ia bekerja seperti biasaassert
, mencetak pesan kesalahan danabort
program, dan dalam rilis build itu menggunakan asumsi, menghasilkan kode yang dioptimalkan.Perhatikan, bagaimanapun, bahwa itu bukan pengganti biasa
assert
-cond
tetap dalam rilis rilis, jadi Anda tidak harus melakukan sesuatu sepertiassume(VeryExpensiveComputation())
.sumber
return 2
cabang dihilangkan dari kode oleh kompiler.__builtin_expect
adalah petunjuk yang tidak ketat.__builtin_expect(e, c)
harus dibaca sebagai "e
paling mungkin untuk dievaluasic
", dan dapat bermanfaat untuk mengoptimalkan prediksi cabang, tetapi tidak terbatase
untuk selalu demikianc
, jadi jangan biarkan pengoptimal untuk membuang kasus lain. Lihatlah bagaimana cabang-cabang diorganisasi dalam pertemuan .__builtin_unreachable()
.assert
, misalnya mendefinisikanassume
sebagaiassert
ketikaNDEBUG
tidak didefinisikan, dan sebagai__builtin_unreachable()
saatNDEBUG
didefinisikan. Dengan begitu Anda mendapatkan manfaat dari asumsi dalam kode produksi, tetapi dalam membangun debug Anda masih memiliki pemeriksaan eksplisit. Tentu saja Anda harus melakukan tes yang cukup untuk meyakinkan diri sendiri bahwa anggapan tersebut akan memuaskan di alam liar.Ada dukungan standar untuk ini. Apa yang harus Anda lakukan adalah memasukkan
stdint.h
(cstdint
) dan kemudian menggunakan jenisnyauint_fast8_t
.Ini memberitahu kompiler bahwa Anda hanya menggunakan angka antara 0 - 255, tetapi itu bebas untuk menggunakan tipe yang lebih besar jika itu memberikan kode lebih cepat. Demikian pula, kompiler dapat mengasumsikan bahwa variabel tidak akan pernah memiliki nilai di atas 255 dan kemudian melakukan optimasi yang sesuai.
sumber
uint_fast8_t
yang sebenarnya merupakan tipe 8-bit (mis.unsigned char
) Seperti pada x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ). Pada DEC Alpha awal sebelum 21164A , byte byte / toko tidak didukung, sehingga implementasi yang waras akan digunakantypedef uint32_t uint_fast8_t
. AFAIK, tidak ada mekanisme untuk tipe untuk memiliki batas jangkauan ekstra dengan kebanyakan kompiler (seperti gcc), jadi saya cukup yakinuint_fast8_t
akan berperilaku sama persis denganunsigned int
atau apa pun dalam kasus itu.bool
khusus, dan rentang-terbatas pada 0 atau 1, tetapi ini adalah tipe bawaan, tidak ditentukan oleh file header dalam halchar
, pada gcc / dentang. Seperti yang saya katakan, saya tidak berpikir kebanyakan kompiler memiliki mekanisme itu akan memungkinkan.)uint_fast8_t
ini adalah rekomendasi yang bagus, karena akan menggunakan tipe 8-bit pada platform di mana itu seefisienunsigned int
. (Aku sebenarnya tidak yakin apafast
jenis seharusnya cepat untuk , dan apakah cache jejak tradeoff seharusnya menjadi bagian dari itu.). x86 memiliki dukungan luas untuk operasi byte, bahkan untuk melakukan byte tambah dengan sumber memori, sehingga Anda bahkan tidak perlu melakukan beban zero-extending terpisah (yang juga sangat murah). gcc membuatuint_fast16_t
tipe 64-bit pada x86, yang gila untuk sebagian besar kegunaan (vs 32-bit). godbolt.org/g/Rmq5bv .Jawaban saat ini baik untuk kasus saat Anda tahu pasti kisarannya, tetapi jika Anda masih menginginkan perilaku yang benar saat nilainya di luar kisaran yang diharapkan, maka tidak akan berfungsi.
Untuk itu, saya menemukan teknik ini dapat bekerja:
Idenya adalah pertukaran kode-data: Anda memindahkan 1 bit data (apakah
x == c
) ke dalam logika kontrol .Ini mengisyaratkan ke pengoptimal yang
x
sebenarnya adalah konstanta yang diketahuic
, mendorongnya untuk menyesuaikan dan mengoptimalkan permintaan pertamafoo
secara terpisah dari yang lain, mungkin cukup berat.Pastikan untuk benar-benar memasukkan kode ke dalam satu subrutin
foo
, - jangan menduplikasi kodenya.Contoh:
Agar teknik ini berfungsi, Anda harus sedikit beruntung - ada kasus di mana kompiler memutuskan untuk tidak mengevaluasi hal-hal secara statis, dan mereka agak sewenang-wenang. Tetapi ketika berhasil, itu bekerja dengan baik:
Cukup gunakan
-O3
dan perhatikan konstanta pra-evaluasi0x20
dan0x30e
dalam output assembler .sumber
if (x==c) foo(c) else foo(x)
? Jika hanya untuk menangkapconstexpr
implementasifoo
?constexpr
adalah suatu hal dan tidak pernah repot-repot untuk "memperbaruinya" setelah itu (meskipun saya tidak pernah benar-benar peduli untuk mengkhawatirkanconstexpr
bahkan sesudahnya), tetapi alasan saya tidak melakukannya pada awalnya adalah bahwa saya ingin membuatnya lebih mudah bagi kompiler untuk memfaktorkannya sebagai kode umum dan menghapus cabang jika memutuskan untuk membiarkan mereka sebagai pemanggilan metode normal dan tidak mengoptimalkan. Saya berharap jika saya memasukkannyac
sangat sulit bagi kompiler untuk c (maaf, lelucon buruk) bahwa keduanya adalah kode yang sama, meskipun saya tidak pernah memverifikasi ini.Saya hanya ingin mengatakan bahwa jika Anda ingin solusi yang lebih standar C ++, Anda dapat menggunakan
[[noreturn]]
atribut untuk menulis sendiriunreachable
.Jadi saya akan mengarahkan kembali contoh bagus deniss ' untuk menunjukkan:
Yang seperti yang Anda lihat , menghasilkan kode yang hampir identik:
Kelemahannya tentu saja, bahwa Anda mendapatkan peringatan bahwa suatu
[[noreturn]]
fungsi memang kembali.sumber
clang
, ketika solusi asli saya tidak , trik yang bagus dan +1. Tetapi semuanya sangat bergantung pada kompiler (seperti yang ditunjukkan oleh Peter Cordes kepada kami,icc
hal itu dapat memperburuk kinerja), sehingga masih belum dapat diterapkan secara universal. Juga, catatan kecil:unreachable
definisi harus tersedia untuk pengoptimal dan inline agar ini berfungsi .