Saya punya beberapa kode yang kurang lebih seperti ini:
#include <bitset>
enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };
void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits[] = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}
bits &= mask;
}
Clang> = 3.6 melakukan hal yang cerdas dan mengkompilasi ini menjadi satu and
instruksi (yang kemudian disisipkan di tempat lain):
apply_known_mask(std::bitset<64ul>&): # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret
Tetapi setiap versi GCC yang saya coba mengkompilasi ini menjadi kekacauan besar yang mencakup penanganan kesalahan yang seharusnya DCE secara statis. Dalam kode lain, ia bahkan akan menempatkan important_bits
padanan sebagai data sesuai dengan kode!
.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)
Bagaimana saya harus menulis kode ini sehingga kedua kompiler dapat melakukan hal yang benar? Jika gagal, bagaimana saya harus menulis ini agar tetap jelas, cepat, dan mudah dipelihara?
c++
c++11
bit-manipulation
Alex Reinking
sumber
sumber
B | D | E | ... | O
?(1ULL << B) | ... | (1ULL << O)
(1ULL << Constant)
| per baris, dan sejajarkan nama konstan pada baris yang berbeda, itu akan lebih mudah dilihat.int
hasil dari operasi bit MUNGKINint
ATAU mungkinlong long
tergantung nilainya dan secara formalenum
tidak setara denganint
konstanta. dentang panggilan untuk "seolah-olah", gcc tetap berteleJawaban:
Versi terbaik adalah c ++ 17:
Kemudian
kembali c ++ 14, kita bisa melakukan trik aneh ini:
atau, jika kita terjebak dengan c ++ 11, kita bisa menyelesaikannya secara rekursif:
Godbolt dengan semua 3 - Anda dapat mengganti CPP_VERSION mendefinisikan, dan mendapatkan perakitan identik.
Dalam praktiknya, saya akan menggunakan yang paling modern yang saya bisa. 14 ketukan 11 karena kita tidak memiliki rekursi dan karenanya panjang simbol O (n ^ 2) (yang dapat meledakkan waktu kompilasi dan penggunaan memori kompilator); 17 mengalahkan 14 karena kompiler tidak harus menghilangkan kode-mati dari array itu, dan trik array itu jelek.
Dari jumlah tersebut 14 adalah yang paling membingungkan. Di sini kita membuat array anonim semua 0s, sedangkan sebagai efek samping mengkonstruksi hasil kita, kemudian membuang array tersebut. Array yang dibuang memiliki angka 0 di dalamnya yang sama dengan ukuran paket kami, ditambah 1 (yang kami tambahkan sehingga kami dapat menangani paket kosong).
Penjelasan rinci tentang apa itu c ++ 14versi sedang dilakukan. Ini adalah trik / peretasan, dan fakta bahwa Anda harus melakukan ini untuk memperluas paket parameter dengan efisiensi di C ++ 14 adalah salah satu alasan mengapa ekspresi lipat ditambahkanc ++ 17.
Paling baik dipahami dari dalam ke luar:
ini hanya diperbarui
r
dengan1<<indexes
untuk indeks tetap.indexes
adalah paket parameter, jadi kita harus mengembangkannya.Sisa pekerjaannya adalah menyediakan paket parameter untuk diperluas
indexes
di dalamnya.Satu langkah keluar:
di sini kita mentransmisikan ekspresi kita ke
void
, menunjukkan kita tidak peduli tentang nilai kembaliannya (kita hanya ingin efek samping dari pengaturanr
- dalam C ++, ekspresi sepertia |= b
juga mengembalikan nilai yang mereka setela
).Kemudian kami menggunakan operator koma
,
dan0
membuangvoid
"nilai", dan mengembalikan nilainya0
. Jadi ini adalah sebuah ekspresi yang nilainya0
dan sebagai efek samping dari menghitung0
set sedikit dir
.Pada titik ini, kami memperluas paket parameter
indexes
. Jadi kami mendapatkan:di
{}
. Penggunaan,
ini bukan untuk operator koma, melainkan pemisah elemen array. Inisizeof...(indexes)+1
0
s, yang juga mengatur bitr
sebagai efek samping. Kami kemudian menetapkan{}
instruksi konstruksi array ke arraydiscard
.Selanjutnya kita mentransmisikan
discard
kevoid
- kebanyakan kompiler akan memperingatkan Anda jika Anda membuat variabel dan tidak pernah membacanya. Semua kompiler tidak akan mengeluh jika Anda mentransmisikannya kevoid
, ini semacam cara untuk mengatakan "Ya, saya tahu, saya tidak menggunakan ini", jadi ini menekan peringatan.sumber
((1ull<<indexes)|...|0ull)
ini adalah "ekspresi lipatan" . Secara khusus ini adalah "lipatan kanan biner" dan Ini harus diuraikan sebagai(pack
op
...
op
init)
Pengoptimalan yang Anda cari tampaknya berupa loop peeling, yang diaktifkan di
-O3
, atau secara manual dengan-fpeel-loops
. Saya tidak yakin mengapa ini berada di bawah lingkup pengelupasan loop daripada pengulangan loop, tetapi mungkin tidak mau membuka gulungan dengan aliran kontrol nonlokal di dalamnya (karena ada, berpotensi, dari pemeriksaan rentang).Secara default, GCC berhenti untuk dapat mengupas semua iterasi, yang tampaknya diperlukan. Secara eksperimental, meneruskan
-O2 -fpeel-loops --param max-peeled-insns=200
(nilai defaultnya adalah 100) menyelesaikan pekerjaan dengan kode asli Anda: https://godbolt.org/z/NNWrgasumber
-O3 -fpeel-loops --param max-peeled-insns=200
gagal ... Itu karena-ftree-slp-vectorize
rupanya.jika hanya menggunakan C ++ 11 adalah suatu keharusan
(&a)[N]
adalah cara untuk menangkap array. Ini memungkinkan Anda untuk menulis satu fungsi rekursif tanpa menggunakan fungsi pembantu sama sekali:menugaskannya ke
constexpr auto
:Uji
Keluaran
seseorang benar-benar harus menghargai kemampuan C ++ untuk menghitung apa pun yang dapat dihitung pada waktu kompilasi. Itu pasti masih mengejutkan saya ( <> ).
Untuk versi C ++ 14 dan C ++ 17 yakk yang lebih baru, jawaban yakk sudah luar biasa mencakupnya.
sumber
apply_known_mask
sebenarnya mengoptimalkan?constexpr
. Dan sementara itu secara teoritis tidak cukup, kami tahu bahwa GCC cukup mampu mengevaluasiconstexpr
seperti yang diinginkan.Saya akan mendorong Anda untuk menulis
EnumSet
tipe yang tepat .Menulis dasar
EnumSet<E>
dalam C ++ 14 (dan seterusnya) berdasarkanstd::uint64_t
itu sepele:Ini memungkinkan Anda untuk menulis kode sederhana:
Di C ++ 11, ini membutuhkan beberapa konvolusi, tetapi tetap memungkinkan:
Dan dipanggil dengan:
Bahkan GCC
and
dengan-O1
mudahnya menghasilkan instruksi di godbolt :sumber
constexpr
kode Anda tidak legal. Maksud saya, beberapa memiliki 2 pernyataan! (C ++ 11 constexpr tersedot)EnumSet<E>
tidak menggunakan nilaiE
sebagai nilai secara langsung, melainkan menggunakan1 << e
. Ini adalah domain yang berbeda sama sekali, yang sebenarnya membuat kelas sangat berharga => tidak ada peluang untuk mengindeks secara tidak sengajae
alih-alih1 << e
.Sejak C ++ 11 Anda juga dapat menggunakan teknik TMP klasik:
Tautan ke Compiler Explorer: https://godbolt.org/z/Gk6KX1
Keuntungan dari pendekatan ini dibandingkan fungsi template constexpr adalah bahwa itu berpotensi sedikit lebih cepat untuk dikompilasi karena aturan Chiel .
sumber
Ada beberapa ide yang jauh untuk 'pintar' di sini. Anda mungkin tidak membantu pemeliharaan dengan mengikuti mereka.
adalah
jauh lebih mudah untuk menulis daripada
?
Maka tidak ada sisa kode yang diperlukan.
sumber