Jika saya memiliki beberapa bilangan bulat n, dan saya ingin mengetahui posisi bit paling signifikan (yaitu, jika bit paling tidak signifikan ada di sebelah kanan, saya ingin mengetahui posisi bit kiri terjauh yaitu 1), apa metode tercepat / paling efisien untuk mencari tahu?
Saya tahu bahwa POSIX mendukung ffs()
metode di strings.h untuk menemukan bit set pertama, tetapi tampaknya tidak ada fls()
metode yang sesuai .
Apakah ada cara yang sangat jelas untuk melakukan ini yang saya lewatkan?
Bagaimana jika Anda tidak dapat menggunakan fungsi POSIX untuk portabilitas?
Sunting: Bagaimana dengan solusi yang bekerja pada arsitektur 32 dan 64 bit (banyak dari daftar kode sepertinya hanya bekerja pada 32 bit int).
Jawaban:
GCC memiliki :
Saya berharap mereka diterjemahkan menjadi sesuatu yang cukup efisien untuk platform Anda saat ini, apakah itu salah satu algoritma bit-twiddling yang mewah, atau instruksi tunggal.
Sebuah trik berguna jika masukan Anda dapat menjadi nol adalah
__builtin_clz(x | 1)
: tanpa syarat pengaturan bit rendah tanpa memodifikasi setiap orang lain membuat output31
untukx=0
, tanpa mengubah output untuk input lain.Untuk menghindari keharusan melakukan itu, opsi Anda yang lain adalah intrinsik khusus platform seperti ARM GCC
__clz
(tidak perlu header), atau x86_lzcnt_u32
pada CPU yang mendukunglzcnt
instruksi. (Berhati-hatilah karenalzcnt
men - decode sepertibsr
pada CPU yang lebih lama daripada melakukan kesalahan, yang memberikan 31-lzcnt untuk input bukan-nol.)Sayangnya tidak ada cara untuk mengambil keuntungan dari berbagai instruksi CLZ pada platform non-x86 yang menentukan hasil untuk input = 0 sebagai 32 atau 64 (sesuai dengan lebar operan). x86 juga
lzcnt
melakukannya, sambilbsr
menghasilkan indeks-bit yang harus dibalik kompilator kecuali Anda menggunakannya31-__builtin_clz(x)
.(The "undefined result" bukanlah C Undefined Behavior, hanya sebuah nilai yang tidak ditentukan. Sebenarnya apapun yang ada di register tujuan saat instruksi dijalankan. AMD mendokumentasikannya, Intel tidak, tapi CPU Intel mengimplementasikan perilaku itu . Tapi itu bukan apa pun yang sebelumnya ada di variabel C yang Anda tetapkan, itu biasanya bukan cara kerja ketika gcc mengubah C menjadi asm. Lihat juga Mengapa memecah "ketergantungan keluaran" dari LZCNT penting? )
sumber
__builtin_ctz
overffs
, yang mengkompilasi ke BSF dan CMOV untuk menangani kasus input-was-zero. Pada arsitektur tanpa implementasi yang cukup singkat (misalnya ARM lama tanpaclz
instruksi), gcc memancarkan panggilan ke fungsi pembantu libgcc.Dengan asumsi Anda menggunakan x86 dan bermain untuk sedikit assembler inline, Intel menyediakan
BSR
instruksi ("bit scan reverse"). Ini cepat di beberapa x86 (dikodekan di mikro pada orang lain). Dari manual:(Jika Anda menggunakan PowerPC, ada
cntlz
instruksi serupa ("hitung nol di depan").)Kode contoh untuk gcc:
Lihat juga tutorial assembler sebaris ini , yang menunjukkan (bagian 9.4) itu jauh lebih cepat daripada kode perulangan.
sumber
Karena 2 ^ N adalah bilangan bulat dengan hanya himpunan bit ke-N (1 << N), mencari posisi (N) dari bit himpunan tertinggi adalah basis log bilangan bulat 2 dari bilangan bulat itu.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Algoritme yang "jelas" ini mungkin tidak transparan untuk semua orang, tetapi ketika Anda menyadari bahwa kode bergeser ke kanan satu bit berulang kali hingga bit paling kiri telah dialihkan (perhatikan bahwa C memperlakukan nilai bukan nol sebagai true) dan mengembalikan angka tersebut pergeseran, itu masuk akal. Ini juga berarti bahwa ia bekerja bahkan ketika lebih dari satu bit disetel - hasilnya selalu untuk bit paling signifikan.
Jika Anda menggulir ke bawah pada halaman itu, ada variasi yang lebih cepat dan lebih kompleks. Namun, jika Anda tahu Anda berurusan dengan angka dengan banyak nol di depan, pendekatan naif dapat memberikan kecepatan yang dapat diterima, karena pergeseran bit agak cepat di C, dan algoritme sederhana tidak memerlukan pengindeksan array.
CATATAN: Saat menggunakan nilai 64-bit, berhati-hatilah saat menggunakan algoritma yang sangat pintar; banyak dari mereka hanya bekerja dengan benar untuk nilai 32-bit.
sumber
>>>
. Ditambah mungkin pembanding!= 0
, dan beberapa jumlah tanda kurung yang tidak ditentukan.Ini harus secepat kilat:
sumber
Ini seperti menemukan semacam log integer. Ada trik yang sedikit memutarbalikkan, tetapi saya telah membuat alat sendiri untuk ini. Tujuannya tentu saja untuk kecepatan.
Kesadaran saya adalah bahwa CPU sudah memiliki detektor bit otomatis, digunakan untuk konversi integer ke float! Jadi gunakan itu.
Versi ini mentransmisikan nilai menjadi dua kali lipat, lalu membaca eksponen, yang memberi tahu Anda di mana bit itu berada. Pergeseran dan pengurangan mewah adalah mengekstrak bagian yang tepat dari nilai IEEE.
Ini sedikit lebih cepat untuk menggunakan pelampung, tetapi pelampung hanya dapat memberi Anda posisi 24 bit pertama karena presisi yang lebih kecil.
Untuk melakukan ini dengan aman, tanpa perilaku tidak terdefinisi di C ++ atau C, gunakan
memcpy
alih-alih casting pointer untuk jenis-punning. Penyusun tahu cara menyebariskannya secara efisien.Atau di C99 dan yang lebih baru, gunakan file
union {double d; uint32_t u[2];};
. Namun perhatikan bahwa di C ++, punning tipe gabungan hanya didukung pada beberapa kompiler sebagai ekstensi, bukan di ISO C ++.Ini biasanya akan lebih lambat daripada intrinsik khusus platform untuk instruksi penghitungan nol terdepan, tetapi ISO C portabel tidak memiliki fungsi seperti itu. Beberapa CPU juga tidak memiliki instruksi penghitungan nol di depan, tetapi beberapa di antaranya dapat secara efisien mengonversi bilangan bulat menjadi
double
. Jenis-punning pola bit FP kembali ke integer bisa lambat, meskipun (misalnya pada PowerPC itu membutuhkan penyimpanan / reload dan biasanya menyebabkan macet-hit-store).Algoritme ini berpotensi berguna untuk implementasi SIMD, karena lebih sedikit CPU yang memiliki SIMD
lzcnt
. x86 hanya mendapat instruksi seperti itu dengan AVX512CDsumber
Kaz Kylheku di sini
Saya membandingkan dua pendekatan untuk angka lebih dari 63 bit ini (tipe panjang panjang di gcc x86_64), menjauh dari bit tanda.
(Saya kebetulan membutuhkan "temukan bit tertinggi" ini untuk sesuatu, Anda tahu.)
Saya menerapkan pencarian biner berbasis data (berdasarkan salah satu jawaban di atas). Saya juga menerapkan pohon keputusan yang sepenuhnya tidak digulung dengan tangan, yang hanya kode dengan operan langsung. Tanpa loop, tidak ada tabel.
Pohon keputusan (tertinggi_bit_unrolled) diukur menjadi 69% lebih cepat, kecuali untuk kasus n = 0 di mana pencarian biner memiliki pengujian eksplisit.
Pengujian khusus pencarian biner untuk kasus 0 hanya 48% lebih cepat daripada pohon keputusan, yang tidak memiliki pengujian khusus.
Kompiler, mesin: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).
Program tes cepat dan kotor:
Dengan hanya menggunakan -O2, perbedaannya menjadi lebih besar. Pohon keputusan hampir empat kali lebih cepat.
Saya juga membandingkan dengan kode pergeseran bit yang naif:
Ini hanya cepat untuk jumlah kecil, seperti yang diharapkan. Dalam menentukan bahwa bit tertinggi adalah 1 untuk n == 1, ia melakukan benchmark lebih dari 80% lebih cepat. Namun, setengah dari angka yang dipilih secara acak dalam ruang 63 bit memiliki kumpulan bit ke-63!
Pada input 0x3FFFFFFFFFFFFFFFF, versi pohon keputusan agak lebih cepat daripada versi 1, dan menunjukkan 1120% lebih cepat (12,2 kali) daripada bit shifter.
Saya juga akan membandingkan pohon keputusan dengan GCC bawaan, dan juga mencoba campuran masukan daripada mengulang dengan nomor yang sama. Mungkin ada beberapa prediksi cabang yang sedang berlangsung dan mungkin beberapa skenario caching yang tidak realistis yang membuatnya lebih cepat secara artifisial pada pengulangan.
sumber
Bagaimana dengan
?
sumber
1 register, 13 instruksi. Percaya atau tidak, ini biasanya lebih cepat daripada instruksi BSR yang disebutkan di atas, yang beroperasi dalam waktu linier. Ini adalah waktu logaritmik.
Dari http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit
sumber
__builtin_clz
jika diaktifkan dengan-march=native
atau sesuatu (karena cepat pada setiap CPU yang mendukungnya). Bahkan pada CPU seperti AMD Bulldozer-family di mana BSR "lambat", ini tidak terlalu lambat: 7 m-op dengan latensi 4 siklus dan satu per 4c throughput. Di Atom, BSR sangat lambat: 16 siklus. Di Silvermont, ini 10 uops dengan 10 siklus latensi. Ini mungkin latensi sedikit lebih rendah daripada BSR di Silvermont, tapi IDK.Berikut adalah beberapa tolok ukur (sederhana), dari algoritma yang saat ini diberikan di halaman ini ...
Algoritma belum diuji pada semua masukan dari unsigned int; jadi periksa dulu, sebelum menggunakan sesuatu secara membabi buta;)
Di mesin saya, clz (__builtin_clz) dan asm bekerja paling baik. asm tampaknya lebih cepat dari clz ... tetapi mungkin karena patokan sederhana ...
sumber
Meskipun saya mungkin hanya akan menggunakan metode ini jika saya benar-benar membutuhkan kinerja terbaik (misalnya untuk menulis semacam AI permainan papan yang melibatkan bitboards), solusi paling efisien adalah menggunakan ASM sebaris. Lihat bagian Pengoptimalan pada entri blog ini untuk kode dengan penjelasan.
sumber
Saya memiliki kebutuhan akan rutinitas untuk melakukan ini dan sebelum mencari web (dan menemukan halaman ini) saya datang dengan solusi saya sendiri berdasarkan pencarian biner. Meskipun saya yakin seseorang telah melakukan ini sebelumnya! Ini berjalan dalam waktu yang konstan dan bisa lebih cepat daripada solusi "jelas" yang diposting, meskipun saya tidak membuat klaim yang bagus, hanya mempostingnya untuk kepentingan.
sumber
itu semacam pencarian biner, ini bekerja dengan semua jenis tipe integer (unsigned!)
untuk melengkapi:
sumber
typedef
s atau memang apa pun kecuali makro praprosesor. Ini adalah konvensi yang diterima secara luas.Beberapa jawaban yang terlalu rumit di sini. Teknik Debruin hanya boleh digunakan ketika input sudah menjadi kekuatan dua, jika tidak, ada cara yang lebih baik. Untuk kekuatan 2 input, Debruin adalah yang tercepat mutlak, bahkan lebih cepat daripada
_BitScanReverse
prosesor mana pun yang saya uji. Namun, dalam kasus umum,_BitScanReverse
(atau apa pun yang disebut intrinsik dalam kompiler Anda) adalah yang tercepat (pada CPU tertentu itu dapat di-microcode).Jika fungsi intrinsik bukan pilihan, berikut adalah solusi perangkat lunak yang optimal untuk memproses input umum.
Perhatikan bahwa versi ini tidak memerlukan pencarian Debruin di bagian akhir, tidak seperti kebanyakan jawaban lainnya. Ini menghitung posisi di tempat.
Tabel bisa lebih disukai meskipun, jika Anda memanggilnya berulang kali cukup sering, risiko cache miss dikalahkan oleh percepatan tabel.
Ini akan menghasilkan throughput tertinggi dari semua jawaban perangkat lunak yang diberikan di sini, tetapi jika Anda hanya memanggilnya sesekali, lebih suka solusi tanpa tabel seperti cuplikan pertama saya.
sumber
Seperti yang ditunjukkan oleh jawaban di atas, ada sejumlah cara untuk menentukan bit yang paling signifikan. Namun, seperti yang juga ditunjukkan, metode ini cenderung unik untuk register 32bit atau 64bit. The Halaman bithacks stanford.edu menyediakan solusi yang bekerja untuk 32bit dan 64bit komputasi. Dengan sedikit kerja, mereka dapat digabungkan untuk memberikan pendekatan lintas arsitektur yang solid untuk mendapatkan MSB. Solusi yang saya temukan yang dikompilasi / bekerja di komputer 64 & 32 bit adalah:
sumber
#ifdef BUILD_64
bendera? Dalam hal ini tidak perlu redefinisi dalam kondisional.Versi di C menggunakan perkiraan berurutan:
Keuntungan: waktu berjalan konstan terlepas dari jumlah yang diberikan, karena jumlah loop selalu sama. (4 loop saat menggunakan "unsigned int")
sumber
msb += (n>>msb) ? step : -step;
), lebih banyak kompiler cenderung membuat asm tanpa cabang, menghindari kesalahan prediksi cabang pada setiap langkah ( stackoverflow.com/questions/11227809/… ).Saya tahu pertanyaan ini sangat tua, tetapi baru saja menerapkan fungsi msb () sendiri, saya menemukan bahwa sebagian besar solusi yang disajikan di sini dan di situs web lain belum tentu yang paling efisien - setidaknya untuk definisi efisiensi pribadi saya (lihat juga Pembaruan di bawah ). Inilah alasannya:
Sebagian besar solusi (terutama yang menggunakan skema pencarian biner atau pendekatan naif yang melakukan pemindaian linier dari kanan ke kiri) tampaknya mengabaikan fakta bahwa untuk bilangan biner arbitrer, tidak banyak yang dimulai dengan urutan yang sangat panjang. nol. Faktanya, untuk lebar bit apa pun, setengah dari semua bilangan bulat dimulai dengan 1 dan seperempatnya dimulai dengan 01 . Lihat kemana tujuanku? Argumen saya adalah bahwa pemindaian linier mulai dari posisi bit yang paling signifikan hingga yang paling tidak signifikan (kiri ke kanan) tidak begitu "linier" seperti yang terlihat pada pandangan pertama.
Dapat ditunjukkan 1 , bahwa untuk setiap lebar bit, jumlah rata-rata bit yang perlu diuji paling banyak 2. Ini diterjemahkan menjadi kompleksitas waktu diamortisasi dari O (1) sehubungan dengan jumlah bit (!) .
Tentu saja, kasus terburuk masih O (n) , lebih buruk daripada O (log (n)) yang Anda dapatkan dengan pendekatan mirip-pencarian biner, tetapi karena ada begitu sedikit kasus terburuk, mereka dapat diabaikan untuk sebagian besar aplikasi ( Perbarui : tidak cukup: Mungkin ada sedikit, tetapi mungkin terjadi dengan probabilitas tinggi - lihat Pembaruan di bawah).
Berikut adalah pendekatan "naif" yang saya buat, yang setidaknya di mesin saya mengalahkan sebagian besar pendekatan lain (skema pencarian biner untuk int 32-bit selalu memerlukan log 2 (32) = 5 langkah, sedangkan algoritme konyol ini membutuhkan lebih sedikit dari rata-rata 2) - maaf karena ini C ++ dan bukan C murni:
Pembaruan : Sementara apa yang saya tulis di sini sangat benar untukbilangan bulat sewenang - wenang , di mana setiap kombinasi bit sama-sama mungkin (tes kecepatan saya hanya mengukur berapa lama waktu yang dibutuhkan untuk menentukan MSB untuk semua bilangan bulat 32-bit), bilangan bulat kehidupan nyata, untuk dimana fungsi seperti itu akan dipanggil, biasanya mengikuti pola yang berbeda: Dalam kode saya, misalnya, fungsi ini digunakan untuk menentukan apakah ukuran objek adalah pangkat 2, atau untuk menemukan pangkat 2 berikutnya lebih besar atau sama dari ukuran objek . Dugaan saya adalah bahwa sebagian besar aplikasi yang menggunakan MSB melibatkan angka yang jauh lebih kecil daripada angka maksimum yang dapat diwakili oleh integer (ukuran objek jarang menggunakan semua bit dalam size_t). Dalam kasus ini, solusi saya sebenarnya akan bekerja lebih buruk daripada pendekatan pencarian biner - jadi yang terakhir mungkin lebih disukai, meskipun solusi saya akan lebih cepat mengulang melalui semua bilangan bulat.
TL; DR: Bilangan bulat kehidupan nyata mungkin akan memiliki bias terhadap kasus terburuk dari algoritma sederhana ini, yang pada akhirnya akan membuatnya berkinerja lebih buruk - terlepas dari kenyataan bahwa itu diamortisasi O (1) untuk bilangan bulat yang benar-benar sewenang-wenang.
1 Argumennya seperti ini (draf kasar): Misalkan n adalah jumlah bit (lebar bit). Ada total 2 n bilangan bulat yang dapat direpresentasikan dengan n bit. Ada 2 n - 1 bilangan bulat yang dimulai dengan 1 ( 1 pertama tetap, sisa n - 1 bit bisa apa saja). Integer tersebut hanya membutuhkan satu interasi loop untuk menentukan MSB. Selanjutnya, ada 2 n - 2 bilangan bulat dimulai dengan 01 , membutuhkan 2 iterasi, 2 n - 3 bilangan bulat dimulai dengan 001 , membutuhkan 3 iterasi, dan seterusnya.
Jika kita menjumlahkan semua iterasi yang diperlukan untuk semua kemungkinan bilangan bulat dan membaginya dengan 2 n , jumlah total bilangan bulat, kita mendapatkan jumlah rata-rata iterasi yang diperlukan untuk menentukan MSB untuk bilangan bulat n- bit:
(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 + ... + n) / 2 n
Rangkaian iterasi rata-rata ini sebenarnya konvergen dan memiliki batas 2 untuk n menuju tak terhingga
Dengan demikian, algoritma kiri-ke-kanan naif sebenarnya memiliki kompleksitas waktu konstan diamortisasi dari O (1) untuk sejumlah bit.
sumber
c99telah memberi kami
log2
. Ini menghilangkan kebutuhan untuk semualog2
penerapan saus khusus yang Anda lihat di halaman ini. Anda dapat menggunakanlog2
implementasi standar seperti ini:Sebuah
n
dari0UL
kebutuhan untuk dijaga terhadap juga, karena:Saya telah menulis sebuah contoh dengan cek bahwa set sewenang-wenang
Index
untukULONG_MAX
sini: https://ideone.com/u26vsiItu Studio visualakibat wajar dari jawaban gcc ephemient adalah:
Dokumentasi untuk
_BitScanReverse
negara bagian yaituIndex
:Dalam prakteknya saya telah menemukan bahwa jika
n
adalah0UL
yangIndex
diatur untuk0UL
, seperti itu akan untukn
dari1UL
. Tetapi satu-satunya hal yang dijamin dalam dokumentasi dalam kasusn
dari0UL
adalah bahwa pengembaliannya adalah:Jadi, serupa dengan
log2
implementasi yang lebih disukai di atas, kembalian harus diperiksa pengaturannyaIndex
ke nilai yang ditandai dalam kasus ini. Saya sekali lagi menulis contoh penggunaanULONG_MAX
untuk nilai bendera ini di sini: http://rextester.com/GCU61409sumber
_BitScanReverse
mengembalikan 0 hanya jika masukannya0
. Ini seperti instruksi x86BSR
, yang menyetel ZF hanya berdasarkan input, bukan output. Menarik bahwa MS mengatakan dokumenindex
tidak disetel saat tidak ada1
bit yang ditemukan; yang juga cocok dengan perilaku asm x86bsr
. (AMD mendokumentasikannya sebagai membiarkan register tujuan tidak dimodifikasi pada src = 0, tetapi Intel hanya mengatakan keluaran yang tidak ditentukan meskipun CPU mereka menerapkan perilaku biarkan-tidak dimodifikasi.) Ini tidak seperti x86lzcnt
, yang memberikan32
untuk tidak ditemukan._BitScanReverse
menggunakan pengindeksan berbasis nol, jadi jikan
1 maka indeks dari bit yang disetel ternyata 0. Sayangnya, seperti yang Anda katakan jikan
0 maka outputnya juga 0 :( Ini berarti tidak ada cara untuk menggunakan kembali ke membedakan antaran
1 atau 0. Itulah yang saya coba komunikasikan. Apakah menurut Anda ada cara yang lebih baik untuk mengatakan ini?Index
. Itu bukan nilai pengembaliannya . Ini mengembalikan boolean yang salah jika inputnya nol (dan inilah mengapa Indeks diteruskan oleh referensi alih-alih dikembalikan secara normal). godbolt.org/g/gQKJdE . Dan saya memeriksa: meskipun kata-kata dalam dokumen MS,_BitScanReverse
tidak membiarkan Indeks tidak diseteln==0
: Anda hanya mendapatkan nilai apa pun di register yang kebetulan digunakannya. (Yang dalam kasus Anda mungkin adalah register yang sama dengan yang digunakanIndex
setelahnya, sehingga Anda melihat a0
).log2
sejak C99.Pikirkan operator bitwise.
Saya salah paham pertanyaan pertama kali. Anda harus menghasilkan int dengan bit set paling kiri (yang lain nol). Dengan asumsi cmp disetel ke nilai itu:
sumber
8
harusCHAR_BIT
. Ini sangat tidak mungkin menjadi cara tercepat, karena kesalahan prediksi cabang akan terjadi saat keluar dari loop kecuali ini digunakan dengan input yang sama berulang kali. Selain itu, untuk input kecil (banyak nol), ia harus melakukan banyak loop. Ini seperti cara fallback yang Anda gunakan sebagai versi yang mudah diverifikasi dalam pengujian unit untuk dibandingkan dengan versi yang dioptimalkan.Memperluas patokan Josh ... seseorang dapat meningkatkan clz sebagai berikut
Mengenai asm: perhatikan bahwa ada bsr dan bsrl (ini adalah versi "panjang"). yang normal mungkin sedikit lebih cepat.
sumber
Perhatikan bahwa apa yang Anda coba lakukan adalah menghitung log2 integer dari sebuah integer,
Perhatikan bahwa Anda dapat mencoba mencari lebih dari 1 bit dalam satu waktu.
Pendekatan ini menggunakan pencarian biner
Metode pencarian biner lain, mungkin lebih mudah dibaca,
Dan karena Anda ingin menguji ini,
sumber
Menempatkan ini karena ini adalah pendekatan 'yang lain', tampaknya berbeda dari yang lain yang sudah diberikan.
mengembalikan
-1
jikax==0
, sebaliknyafloor( log2(x))
(hasil maksimal 31)Kurangi dari masalah 32 menjadi 4 bit, lalu gunakan tabel. Mungkin janggal, tapi pragmatis.
Inilah yang saya gunakan ketika saya tidak ingin menggunakan
__builtin_clz
karena masalah portabilitas.Untuk membuatnya lebih kompak, seseorang dapat menggunakan loop untuk mengurangi, menambahkan 4 ke r setiap kali, maks 7 iterasi. Atau beberapa hybrid, seperti (untuk 64 bit): loop untuk dikurangi menjadi 8, uji untuk mengurangi menjadi 4.
sumber
Wah, itu banyak sekali jawaban. Saya tidak menyesal menjawab pertanyaan lama.
Jawaban ini sangat mirip dengan jawaban lain ... oh baiklah.
sumber
1<<k
adalah sentuhan yang bagus. Bagaimana dengan topengnya?(1 << (1<<k-1)-1<< (1<<k-1)
? (most optimal
? Anda membandingkan superlatif?)&
dan&~
.) Anda dapat mengganti konstanta hex dengan cara((type)1<<(1<<k))-1<<(1<<k)
.Kode:
Atau dapatkan bagian integer dari instruksi FPU FYL2X (Y * Log2 X) dengan mengatur Y = 1
sumber
double
, yang mungkin bagus jika itu benar-benar menyimpan / memuat ulang daripada jenis-pun dengan cara lain, misalnya denganmovq
instruksi seperti yang mungkin Anda dapatkan di sini pada x86.[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
.Poster lain menyediakan tabel pencarian menggunakan pencarian lebar byte . Jika Anda ingin menambah kinerja (dengan biaya memori 32K daripada hanya 256 entri pencarian) berikut adalah solusi menggunakan tabel pencarian 15-bit , di C # 7 untuk .NET .
Bagian yang menarik adalah menginisialisasi tabel. Karena ini adalah blok yang relatif kecil yang kami inginkan selama masa proses, saya mengalokasikan memori yang tidak terkelola untuk ini dengan menggunakan
Marshal.AllocHGlobal
. Seperti yang Anda lihat, untuk performa maksimal, seluruh contoh ditulis sebagai native:Tabel membutuhkan inisialisasi satu kali melalui kode di atas. Ini hanya-baca sehingga satu salinan global dapat dibagikan untuk akses bersamaan. Dengan tabel ini, Anda dapat dengan cepat mencari log bilangan bulat 2 , yang kita cari di sini, untuk semua lebar bilangan bulat yang bervariasi (8, 16, 32, dan 64 bit).
Perhatikan bahwa entri tabel untuk
0
, satu-satunya bilangan bulat yang gagasan 'set bit tertinggi' tidak ditentukan, diberi nilai-1
. Pembedaan ini diperlukan untuk penanganan yang tepat atas kata-kata atas bernilai 0 pada kode di bawah ini. Tanpa basa-basi lagi, berikut adalah kode untuk masing-masing primitif integer:ulong (64-bit) Versi
Versi uint (32-bit)
Berbagai kelebihan beban di atas
Ini adalah solusi kerja lengkap yang mewakili kinerja terbaik pada .NET 4.7.2 untuk banyak alternatif yang saya bandingkan dengan harness uji kinerja khusus. Beberapa di antaranya disebutkan di bawah ini. Parameter uji adalah kerapatan seragam dari semua posisi 65 bit, yaitu, nilai 0 ... 31/63 plus
0
(yang menghasilkan hasil -1). Bit di bawah posisi indeks target diisi secara acak. Pengujiannya hanya x64 , mode rilis, dengan pengoptimalan JIT diaktifkan.Itulah akhir dari jawaban formal saya di sini; berikut ini adalah beberapa catatan santai dan tautan ke kode sumber untuk kandidat tes alternatif yang terkait dengan pengujian yang saya jalankan untuk memvalidasi kinerja dan kebenaran kode di atas.
Versi yang disediakan di atas, dikodekan sebagai Tab16A adalah pemenang yang konsisten atas banyak proses. Berbagai kandidat ini, dalam bentuk kerja / awal aktif, dapat ditemukan di sini , di sini , dan di sini .
Yang perlu diperhatikan adalah kinerja mengerikan
ntdll.dll!RtlFindMostSignificantBit
via P / Invoke:Ini sangat buruk, karena inilah seluruh fungsi sebenarnya:
Saya tidak bisa membayangkan kinerja buruk yang berasal dari lima baris ini, jadi hukuman transisi yang dikelola / asli harus disalahkan. Saya juga terkejut bahwa pengujian tersebut benar-benar menyukai
short
tabel pencarian langsung 32KB (dan 64KB) (16-bit) daripada tabel pencarian 128-byte (dan 256-byte)byte
(8-bit). Saya pikir yang berikut akan lebih kompetitif dengan pencarian 16-bit, tetapi yang terakhir secara konsisten mengungguli ini:Hal terakhir yang akan saya tunjukkan adalah saya cukup terkejut bahwa metode deBruijn saya tidak berjalan lebih baik. Ini adalah metode yang sebelumnya saya gunakan secara luas:
Ada banyak diskusi tentang bagaimana metode deBruijn yang superior dan hebat pada pertanyaan SO ini , dan saya cenderung setuju. Spekulasi saya adalah, meskipun metode tabel deBruijn dan tabel pencarian langsung (yang menurut saya paling cepat) keduanya harus melakukan pencarian tabel, dan keduanya memiliki percabangan yang sangat minimal, hanya deBruijn yang memiliki operasi penggandaan 64-bit. Saya hanya menguji
IndexOfMSB
fungsinya di sini - bukan deBruijn --tetapiIndexOfLSB
saya berharap deBruijn memiliki peluang yang jauh lebih baik karena memiliki lebih sedikit operasi (lihat di atas), dan saya kemungkinan akan terus menggunakannya untuk LSB.sumber
Metode saya yang sederhana sangat sederhana:
MSB (x) = INT [Log (x) / Log (2)]
Terjemahan: MSB dari x adalah nilai integer (Log dari Base x dibagi dengan Log dari Base 2).
Ini dapat dengan mudah dan cepat disesuaikan dengan bahasa pemrograman apa pun. Cobalah di kalkulator Anda untuk melihat sendiri bahwa ini berfungsi.
sumber
int(math.log((1 << 48) - 1) / math.log(2))
adalah 48.Berikut adalah solusi cepat untuk C yang berfungsi di GCC dan Clang ; siap untuk disalin dan ditempel.
Dan versi yang sedikit ditingkatkan untuk C ++ .
Kode berasumsi bahwa
value
itu tidak akan terjadi0
. Jika Anda ingin memperbolehkan 0, Anda perlu mengubahnya.sumber
Saya berasumsi pertanyaan Anda adalah untuk integer (disebut v di bawah) dan bukan integer unsigned.
Jika Anda ingin membuatnya bekerja tanpa memperhitungkan tanda, Anda dapat menambahkan 'v << = 1;' ekstra sebelum loop (dan ubah nilai r menjadi 30 sesuai). Tolong beritahu saya jika saya lupa sesuatu. Saya belum mengujinya tetapi seharusnya berfungsi dengan baik.
sumber
v <<= 1
adalah perilaku tidak terdefinisi (UB) saatv < 0
.0x8000000
, mungkin maksud Anda tambahan 0 di sana.