私は、整数に設定されている最下位ビットの位置を決定する効率的な方法を探しています。 0x0FF0の場合は4です。
簡単な実装は次のとおりです。
unsigned GetLowestBitPos(unsigned value)
{
assert(value != 0); // handled separately
unsigned pos = 0;
while (!(value & 1))
{
value >>= 1;
++pos;
}
return pos;
}
それからいくつかのサイクルを絞り出す方法はありますか?
(注:この質問は、そのようなことを楽しむ人のためのものであり、xyzoptimizationが悪であると言う人のためではありません。)
[編集]アイデアをありがとう!他にもいくつかのことを学びました。クール!
Bit Twiddling Hacks は、パフォーマンス/最適化の説明が添付された、優れたビット調整ハックのコレクションを提供します。 (そのサイトからの)あなたの問題に対する私のお気に入りのソリューションは、"乗算とルックアップ"です。
unsigned int v; // find the number of trailing zeros in 32-bit v
int r; // result goes here
static const int MultiplyDeBruijnBitPosition[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];
参考資料:
組み込みの ffs を使用しないのはなぜですか? (Linuxからmanページを入手しましたが、それよりも広く入手可能です。)
ffs(3)-Linux manページ
名前
ffs-Wordの最初のビットセットを見つける
あらすじ
#include <strings.h> int ffs(int i); #define _GNU_SOURCE #include <string.h> int ffsl(long int i); int ffsll(long long int i);
説明
Ffs()関数は、Word iで設定された最初の(最下位)ビットの位置を返します。最下位ビットは位置1で、最上位ビットは例えば32または64。関数ffsll()およびffsl()は同じことを行いますが、サイズが異なる可能性のある引数を取ります。
戻り値
これらの関数は、最初のビットセットの位置を返します。iにビットが設定されていない場合は0を返します。
に準拠
4.3BSD、POSIX.1-2001。
ノート
BSDシステムのプロトタイプは
<string.h>
。
それを行うx86アセンブリ命令(bsf
)があります。 :)
より最適化されましたか?!
このレベルでの最適化は、本質的にアーキテクチャに依存します。現在のプロセッサは複雑すぎる(分岐予測、キャッシュミス、パイプラインの観点から)であるため、どのアーキテクチャでどのコードがより速く実行されるかを予測するのは非常に困難です。操作を32から9に減らすと、一部のアーキテクチャでパフォーマンスが低下する可能性があります。単一のアーキテクチャで最適化されたコードは、他のアーキテクチャでより悪いコードになる可能性があります。特定のCPU向けにこれを最適化するか、そのままにして、コンパイラにそれが良いと思うものを選択させると思います。
ほとんどの最新のアーキテクチャには、最低セットビット、最高セットビットの位置を見つけたり、先行ゼロの数をカウントしたりするための何らかの命令があります。
このクラスのいずれかの命令がある場合、他の命令を安価にエミュレートできます。
少し時間をかけて紙の上で作業を進め、x & (x-1)
がxの最下位のセットビットをクリアし、( x & ~(x-1) )
は、構造、ワード長などに関係なく、最下位のセットビットのみを返します。これを知っているため、明示的な命令がない場合、ハードウェアカウント先行ゼロ/最上位セットビットを使用して最下位セットビットを見つけることは簡単ですそうするために。
関連するハードウェアサポートがまったくない場合は、 here または Bit Twiddling Hacks ページのいずれかで指定されたcount-leading-zeroesの乗算とルックアップの実装上記の識別情報を使用して、最も低いビットを設定するために簡単に変換でき、ブランチレスであるという利点があります。
ウィー、ソリューションの負荷ではなく、目前のベンチマーク。あなたの人々はあなた自身を恥じるべきです;-)
私のマシンはWindows 7 64ビットを実行しているIntel i530(2.9 GHz)です。 MinGWの32ビットバージョンでコンパイルしました。
$ gcc --version
gcc.exe (GCC) 4.7.2
$ gcc bench.c -o bench.exe -std=c99 -Wall -O2
$ bench
Naive loop. Time = 2.91 (Original questioner)
De Bruijn multiply. Time = 1.16 (Tykhyy)
Lookup table. Time = 0.36 (Andrew Grant)
FFS instruction. Time = 0.90 (ephemient)
Branch free mask. Time = 3.48 (Dan / Jim Balter)
Double hack. Time = 3.41 (DocMax)
$ gcc bench.c -o bench.exe -std=c99 -Wall -O2 -march=native
$ bench
Naive loop. Time = 2.92
De Bruijn multiply. Time = 0.47
Lookup table. Time = 0.35
FFS instruction. Time = 0.68
Branch free mask. Time = 3.49
Double hack. Time = 0.92
私のコード:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAY_SIZE 65536
#define NUM_ITERS 5000 // Number of times to process array
int find_first_bits_naive_loop(unsigned nums[ARRAY_SIZE])
{
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
unsigned value = nums[i];
if (value == 0)
continue;
unsigned pos = 0;
while (!(value & 1))
{
value >>= 1;
++pos;
}
total += pos + 1;
}
}
return total;
}
int find_first_bits_de_bruijn(unsigned nums[ARRAY_SIZE])
{
static const int MultiplyDeBruijnBitPosition[32] =
{
1, 2, 29, 3, 30, 15, 25, 4, 31, 23, 21, 16, 26, 18, 5, 9,
32, 28, 14, 24, 22, 20, 17, 8, 27, 13, 19, 7, 12, 6, 11, 10
};
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
unsigned int c = nums[i];
total += MultiplyDeBruijnBitPosition[((unsigned)((c & -c) * 0x077CB531U)) >> 27];
}
}
return total;
}
unsigned char lowestBitTable[256];
int get_lowest_set_bit(unsigned num) {
unsigned mask = 1;
for (int cnt = 1; cnt <= 32; cnt++, mask <<= 1) {
if (num & mask) {
return cnt;
}
}
return 0;
}
int find_first_bits_lookup_table(unsigned nums[ARRAY_SIZE])
{
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
unsigned int value = nums[i];
// note that order to check indices will depend whether you are on a big
// or little endian machine. This is for little-endian
unsigned char *bytes = (unsigned char *)&value;
if (bytes[0])
total += lowestBitTable[bytes[0]];
else if (bytes[1])
total += lowestBitTable[bytes[1]] + 8;
else if (bytes[2])
total += lowestBitTable[bytes[2]] + 16;
else
total += lowestBitTable[bytes[3]] + 24;
}
}
return total;
}
int find_first_bits_ffs_instruction(unsigned nums[ARRAY_SIZE])
{
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
total += __builtin_ffs(nums[i]);
}
}
return total;
}
int find_first_bits_branch_free_mask(unsigned nums[ARRAY_SIZE])
{
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
unsigned value = nums[i];
int i16 = !(value & 0xffff) << 4;
value >>= i16;
int i8 = !(value & 0xff) << 3;
value >>= i8;
int i4 = !(value & 0xf) << 2;
value >>= i4;
int i2 = !(value & 0x3) << 1;
value >>= i2;
int i1 = !(value & 0x1);
int i0 = (value >> i1) & 1? 0 : -32;
total += i16 + i8 + i4 + i2 + i1 + i0 + 1;
}
}
return total;
}
int find_first_bits_double_hack(unsigned nums[ARRAY_SIZE])
{
int total = 0; // Prevent compiler from optimizing out the code
for (int j = 0; j < NUM_ITERS; j++) {
for (int i = 0; i < ARRAY_SIZE; i++) {
unsigned value = nums[i];
double d = value ^ (value - !!value);
total += (((int*)&d)[1]>>20)-1022;
}
}
return total;
}
int main() {
unsigned nums[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++) {
nums[i] = Rand() + (Rand() << 15);
}
for (int i = 0; i < 256; i++) {
lowestBitTable[i] = get_lowest_set_bit(i);
}
clock_t start_time, end_time;
int result;
start_time = clock();
result = find_first_bits_naive_loop(nums);
end_time = clock();
printf("Naive loop. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
start_time = clock();
result = find_first_bits_de_bruijn(nums);
end_time = clock();
printf("De Bruijn multiply. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
start_time = clock();
result = find_first_bits_lookup_table(nums);
end_time = clock();
printf("Lookup table. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
start_time = clock();
result = find_first_bits_ffs_instruction(nums);
end_time = clock();
printf("FFS instruction. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
start_time = clock();
result = find_first_bits_branch_free_mask(nums);
end_time = clock();
printf("Branch free mask. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
start_time = clock();
result = find_first_bits_double_hack(nums);
end_time = clock();
printf("Double hack. Time = %.2f, result = %d\n",
(end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
}
これに対する最速の(非組み込み/非アセンブラー)ソリューションは、最下位バイトを検索し、そのバイトを256エントリのルックアップテーブルで使用することです。これにより、4つの条件付き命令のワーストケースパフォーマンスと1のベストケースが得られます。これは、命令の最小量であるだけでなく、最新のハードウェアで非常に重要なブランチの最小量です。
テーブル(256の8ビットエントリ)には、0〜255の範囲の各番号のLSBのインデックスが含まれている必要があります。値の各バイトをチェックして、ゼロ以外の最下位バイトを見つけ、この値を使用して実際のインデックスを検索します。
これには256バイトのメモリが必要ですが、この関数の速度が非常に重要な場合は、256バイトで十分です。
例えば。
byte lowestBitTable[256] = {
.... // left as an exercise for the reader to generate
};
unsigned GetLowestBitPos(unsigned value)
{
// note that order to check indices will depend whether you are on a big
// or little endian machine. This is for little-endian
byte* bytes = (byte*)value;
if (bytes[0])
return lowestBitTable[bytes[0]];
else if (bytes[1])
return lowestBitTable[bytes[1]] + 8;
else if (bytes[2])
return lowestBitTable[bytes[2]] + 16;
else
return lowestBitTable[bytes[3]] + 24;
}
OMGはこれをらせん状にしています。
これらの例のほとんどに欠けているのは、すべてのハードウェアがどのように機能するかについて少し理解していることです。
ブランチがあるときはいつでも、CPUはどのブランチが採用されるかを推測する必要があります。命令パイプには、推測されたパスをたどる命令がロードされます。 CPUが間違っていると推測した場合、命令パイプがフラッシュされ、他のブランチをロードする必要があります。
上部の単純なwhileループを検討してください。推測はループ内にとどまることです。ループを抜けると、少なくとも一度は間違っています。これにより、命令パイプがフラッシュされます。この動作は、ループから抜けると推測するよりもわずかに優れています。この場合、反復ごとに命令パイプをフラッシュします。
失われるCPUサイクルの量は、プロセッサの種類によって大きく異なります。ただし、20〜150のCPUサイクルの損失が予想されます。
次に悪いグループは、値をより小さな部分に分割し、さらにいくつかのブランチを追加することで、いくつかの反復を節約すると思う場所です。これらの分岐はそれぞれ、命令パイプをフラッシュする追加の機会を追加し、さらに20〜150クロックサイクルかかります。
テーブル内の値を検索するとどうなるかを考えてみましょう。おそらく、関数が最初に呼び出されたときを除いて、値が現在キャッシュにない可能性があります。これは、値がキャッシュからロードされている間にCPUがストールすることを意味します。繰り返しますが、これはマシンによって異なります。新しいIntelチップは、実際には、現在のスレッドがキャッシュの読み込みが完了するのを待っている間にスレッドを交換する機会としてこれを使用します。これは、命令パイプフラッシュよりも簡単に高価になる可能性がありますが、この操作を何度も実行している場合は、1回しか発生しない可能性があります。
明らかに、最速の一定時間ソリューションは決定論的数学を含むものです。純粋でエレガントなソリューション。
これがすでにカバーされている場合、私の謝罪。
XCODE AFAIKを除く私が使用するすべてのコンパイラには、フォワードビットスキャンとリバースビットスキャンの両方のコンパイラ組み込み関数があります。これらは、キャッシュミス、分岐ミス予測、および他のプログラマーがつまずきブロックを生成することなく、ほとんどのハードウェアで単一のAssembly命令にコンパイルされます。
Microsoftコンパイラの場合は、_BitScanForwardおよび_BitScanReverseを使用します。
GCCでは、__ builtin_ffs、__ builtin_clz、__ builtin_ctzを使用します。
また、議論されている主題について十分な知識がない場合は、回答を投稿したり、新規参入者を誤解させたりすることを控えてください。
申し訳ありませんが、ソリューションを提供するのを完全に忘れてしまいました。これは、タスクのアセンブリレベルの指示がないIPADで使用するコードです。
unsigned BitScanLow_BranchFree(unsigned value)
{
bool bwl = (value & 0x0000ffff) == 0;
unsigned I1 = (bwl * 15);
value = (value >> I1) & 0x0000ffff;
bool bbl = (value & 0x00ff00ff) == 0;
unsigned I2 = (bbl * 7);
value = (value >> I2) & 0x00ff00ff;
bool bnl = (value & 0x0f0f0f0f) == 0;
unsigned I3 = (bnl * 3);
value = (value >> I3) & 0x0f0f0f0f;
bool bsl = (value & 0x33333333) == 0;
unsigned I4 = (bsl * 1);
value = (value >> I4) & 0x33333333;
unsigned result = value + I1 + I2 + I3 + I4 - 1;
return result;
}
ここで理解すべきことは、高価なのは比較ではなく、比較後に発生する分岐であるということです。この場合の比較は、.. == 0を使用して0または1の値に強制され、その結果は、ブランチの両側で発生した計算を結合するために使用されます。
編集:
上記のコードは完全に壊れています。このコードは機能し、ブランチはありません(最適化されている場合):
int BitScanLow_BranchFree(ui value)
{
int i16 = !(value & 0xffff) << 4;
value >>= i16;
int i8 = !(value & 0xff) << 3;
value >>= i8;
int i4 = !(value & 0xf) << 2;
value >>= i4;
int i2 = !(value & 0x3) << 1;
value >>= i2;
int i1 = !(value & 0x1);
int i0 = (value >> i1) & 1? 0 : -32;
return i16 + i8 + i4 + i2 + i1 + i0;
}
0を指定すると-1が返されます。0を気にしないか、0で31を取得しても問題ない場合は、i0計算を削除して時間を節約します。
この類似の投稿 に触発され、セットビットの検索が含まれます。以下を提供します。
unsigned GetLowestBitPos(unsigned value)
{
double d = value ^ (value - !!value);
return (((int*)&d)[1]>>20)-1023;
}
長所:
短所:
更新:コメントで指摘されているように、ユニオンは(少なくともCでは)よりクリーンな実装であり、次のようになります。
unsigned GetLowestBitPos(unsigned value)
{
union {
int i[2];
double d;
} temp = { .d = value ^ (value - !!value) };
return (temp.i[1] >> 20) - 1023;
}
これは、すべてのリトルエンディアンストレージを備えた32ビットintを想定しています(x86プロセッサを考えてください)。
2回未満の操作という最悪のケースでも可能です
原理: 2ビット以上のチェックは、1ビットのチェックと同じくらい効率的です。
したがって、たとえば、どのグループ化を最初にチェックし、次にそのグループの最小から最大まで各ビットをチェックすることを妨げるものは何もありません。
そう...
最悪の場合(Nbits/2)+ 1チェック合計で一度に2ビットをチェックする場合。
一度に3ビットをチェックすると、最悪の場合(Nbits/3)+ 2チェックの合計があります。
...
4つのグループをチェックインするのが最適です。最悪の場合、32の代わりに11の操作が必要になります。
最適なケースは、このグループ化のアイデアを使用する場合、アルゴリズムの1チェックから2チェックになります。しかし、ベストケースでの追加の1チェックは、ワーストケースの節約に値します。
注:ループを使用する代わりに完全に記述します。その方が効率的です。
int getLowestBitPos(unsigned int value)
{
//Group 1: Bits 0-3
if(value&0xf)
{
if(value&0x1)
return 0;
else if(value&0x2)
return 1;
else if(value&0x4)
return 2;
else
return 3;
}
//Group 2: Bits 4-7
if(value&0xf0)
{
if(value&0x10)
return 4;
else if(value&0x20)
return 5;
else if(value&0x40)
return 6;
else
return 7;
}
//Group 3: Bits 8-11
if(value&0xf00)
{
if(value&0x100)
return 8;
else if(value&0x200)
return 9;
else if(value&0x400)
return 10;
else
return 11;
}
//Group 4: Bits 12-15
if(value&0xf000)
{
if(value&0x1000)
return 12;
else if(value&0x2000)
return 13;
else if(value&0x4000)
return 14;
else
return 15;
}
//Group 5: Bits 16-19
if(value&0xf0000)
{
if(value&0x10000)
return 16;
else if(value&0x20000)
return 17;
else if(value&0x40000)
return 18;
else
return 19;
}
//Group 6: Bits 20-23
if(value&0xf00000)
{
if(value&0x100000)
return 20;
else if(value&0x200000)
return 21;
else if(value&0x400000)
return 22;
else
return 23;
}
//Group 7: Bits 24-27
if(value&0xf000000)
{
if(value&0x1000000)
return 24;
else if(value&0x2000000)
return 25;
else if(value&0x4000000)
return 26;
else
return 27;
}
//Group 8: Bits 28-31
if(value&0xf0000000)
{
if(value&0x10000000)
return 28;
else if(value&0x20000000)
return 29;
else if(value&0x40000000)
return 30;
else
return 31;
}
return -1;
}
バイナリ検索 を使用しないのはなぜですか?これは、5つの操作の後に常に完了します(intバイトが4バイトであると仮定):
if (0x0000FFFF & value) {
if (0x000000FF & value) {
if (0x0000000F & value) {
if (0x00000003 & value) {
if (0x00000001 & value) {
return 1;
} else {
return 2;
}
} else {
if (0x0000004 & value) {
return 3;
} else {
return 4;
}
}
} else { ...
} else { ...
} else { ...
Chess Programming BitScan page および私自身の測定によると、減算とxorは、否定とマスクよりも高速です。
(0
の末尾のゼロをカウントする場合、メソッドは63
を返しますが、否定とマスクは0
を返します。)
64ビットの減算とxorは次のとおりです。
unsigned long v; // find the number of trailing zeros in 64-bit v
int r; // result goes here
static const int MultiplyDeBruijnBitPosition[64] =
{
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61,
54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62,
46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v ^ (v-1)) * 0x03F79D71B4CB0A89U)) >> 58];
参考までに、64ビットバージョンのnegate and maskメソッドを以下に示します。
unsigned long v; // find the number of trailing zeros in 64-bit v
int r; // result goes here
static const int MultiplyDeBruijnBitPosition[64] =
{
0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x03F79D71B4CB0A89U)) >> 58];
別の方法(モジュラス除算とルックアップ)は、@ anton-tykhyyが提供するものと同じ link からの特別な言及に値します。この方法は、パフォーマンスはDeBruijn乗算およびルックアップ方法と非常に似ていますが、わずかながら重要な違いがあります。
モジュラス除算とルックアップ
unsigned int v; // find the number of trailing zeros in v
int r; // put the result in r
static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
{
32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
20, 8, 19, 18
};
r = Mod37BitPosition[(-v & v) % 37];
モジュラス除算およびルックアップメソッドはv = 0x00000000とv = FFFFFFFFに対して異なる値を返しますが、DeBruijn乗算およびルックアップメソッドは両方の入力でゼロを返します。
テスト:-
unsigned int n1=0x00000000, n2=0xFFFFFFFF;
MultiplyDeBruijnBitPosition[((unsigned int )((n1 & -n1) * 0x077CB531U)) >> 27]); /* returns 0 */
MultiplyDeBruijnBitPosition[((unsigned int )((n2 & -n2) * 0x077CB531U)) >> 27]); /* returns 0 */
Mod37BitPosition[(((-(n1) & (n1))) % 37)]); /* returns 32 */
Mod37BitPosition[(((-(n2) & (n2))) % 37)]); /* returns 0 */
least有意セットビットを見つけることを除いて、単一のx86命令でそれを行う方法については私の答え here を参照してください'BSF
の代わりにBSR
(「ビットスキャンフォワード」)命令が必要です。
さらに別のソリューションは、おそらく最速ではありませんが、非常に良いようです。
少なくともブランチはありません。 ;)
uint32 x = ...; // 0x00000001 0x0405a0c0 0x00602000
x |= x << 1; // 0x00000003 0x0c0fe1c0 0x00e06000
x |= x << 2; // 0x0000000f 0x3c3fe7c0 0x03e1e000
x |= x << 4; // 0x000000ff 0xffffffc0 0x3fffe000
x |= x << 8; // 0x0000ffff 0xffffffc0 0xffffe000
x |= x << 16; // 0xffffffff 0xffffffc0 0xffffe000
// now x is filled with '1' from the least significant '1' to bit 31
x = ~x; // 0x00000000 0x0000003f 0x00001fff
// now we have 1's below the original least significant 1
// let's count them
x = x & 0x55555555 + (x >> 1) & 0x55555555;
// 0x00000000 0x0000002a 0x00001aaa
x = x & 0x33333333 + (x >> 2) & 0x33333333;
// 0x00000000 0x00000024 0x00001444
x = x & 0x0f0f0f0f + (x >> 4) & 0x0f0f0f0f;
// 0x00000000 0x00000006 0x00000508
x = x & 0x00ff00ff + (x >> 8) & 0x00ff00ff;
// 0x00000000 0x00000006 0x0000000d
x = x & 0x0000ffff + (x >> 16) & 0x0000ffff;
// 0x00000000 0x00000006 0x0000000d
// least sign.bit pos. was: 0 6 13
下位ビットが設定されているかどうかを確認できます。その場合は、残りのビットの下位を見てください。例えば。、:
32ビット整数-最初の16のいずれかが設定されているかどうかを確認します。その場合、最初の8つが設定されているかどうかを確認します。もしそうなら、....
そうでない場合は、上位16のいずれかが設定されているかどうかを確認してください。
基本的にはバイナリ検索です。
unsigned GetLowestBitPos(unsigned value)
{
if (value & 1) return 1;
if (value & 2) return 2;
if (value & 4) return 3;
if (value & 8) return 4;
if (value & 16) return 5;
if (value & 32) return 6;
if (value & 64) return 7;
if (value & 128) return 8;
if (value & 256) return 9;
if (value & 512) return 10;
if (value & 1024) return 11;
if (value & 2048) return 12;
if (value & 4096) return 13;
if (value & 8192) return 14;
if (value & 16384) return 15;
if (value & 32768) return 16;
if (value & 65536) return 17;
if (value & 131072) return 18;
if (value & 262144) return 19;
if (value & 524288) return 20;
if (value & 1048576) return 21;
if (value & 2097152) return 22;
if (value & 4194304) return 23;
if (value & 8388608) return 24;
if (value & 16777216) return 25;
if (value & 33554432) return 26;
if (value & 67108864) return 27;
if (value & 134217728) return 28;
if (value & 268435456) return 29;
if (value & 536870912) return 30;
return 31;
}
すべての数値の50%がコードの最初の行に戻ります。
すべての数値の75%は、コードの最初の2行で返されます。
すべての数値の87%は、コードの最初の3行で返されます。
すべての数値の94%は、コードの最初の4行で返されます。
すべての数値の97%がコードの最初の5行で返されます。
等.
このコードの最悪のシナリオがいかに非効率的であるかについて不満を述べている人々は、その状態がどれほどまれに起こるかを理解していないと思います。
O(log(n)) nビット数の時間。[ログ付き]で行う「プログラミングの技術、パート4」で「マジックマスク」を使用したこの巧妙なトリックを見つけました。 (n)余分なスペース]。セットビットをチェックする典型的なソリューションは、O(n)またはneed O(n)ルックアップのための余分なスペースです。テーブルなので、これは良い妥協です。
魔法のマスク:
m0 = (...............01010101)
m1 = (...............00110011)
m2 = (...............00001111)
m3 = (.......0000000011111111)
....
重要なアイデア: x = 1の末尾のゼロの数* [(x&m0)= 0] + 2 * [(x&m1)= 0] + 4 * [(x&m2)= 0] + ...
int lastSetBitPos(const uint64_t x) {
if (x == 0) return -1;
//For 64 bit number, log2(64)-1, ie; 5 masks needed
int steps = log2(sizeof(x) * 8); assert(steps == 6);
//magic masks
uint64_t m[] = { 0x5555555555555555, // .... 010101
0x3333333333333333, // .....110011
0x0f0f0f0f0f0f0f0f, // ...00001111
0x00ff00ff00ff00ff, //0000000011111111
0x0000ffff0000ffff,
0x00000000ffffffff };
//Firstly extract only the last set bit
uint64_t y = x & -x;
int trailZeros = 0, i = 0 , factor = 0;
while (i < steps) {
factor = ((y & m[i]) == 0 ) ? 1 : 0;
trailZeros += factor * pow(2,i);
++i;
}
return (trailZeros+1);
}
C++ 11が利用可能な場合、コンパイラがタスクを実行できる場合があります:)
constexpr std::uint64_t lssb(const std::uint64_t value)
{
return !value ? 0 : (value % 2 ? 1 : lssb(value >> 1) + 1);
}
結果は1から始まるインデックスです。
ログを見つけるのは少しコストがかかりますが、ここに1つの簡単な選択肢があります。
if(n == 0)
return 0;
return log2(n & -n)+1; //Assuming the bit index starts from 1
これは、@ Anton Tykhyyの回答に関するものです
以下は、C++ 11 constexpr実装でキャストを廃止し、64ビットの結果を32ビットに切り捨ててVC++ 17の警告を削除する方法です。
constexpr uint32_t DeBruijnSequence[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
constexpr uint32_t ffs ( uint32_t value )
{
return DeBruijnSequence[
(( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
>> 27];
}
0x1と0x0の両方が0を返すという問題を回避するには、次のようにします。
constexpr uint32_t ffs ( uint32_t value )
{
return (!value) ? 32 : DeBruijnSequence[
(( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
>> 27];
}
しかし、コンパイラーが呼び出しを前処理できないか、前処理しない場合、計算に数サイクルが追加されます。
最後に、興味がある場合、コードが意図したとおりに動作することを確認するための静的アサートのリストを以下に示します。
static_assert (ffs(0x1) == 0, "Find First Bit Set Failure.");
static_assert (ffs(0x2) == 1, "Find First Bit Set Failure.");
static_assert (ffs(0x4) == 2, "Find First Bit Set Failure.");
static_assert (ffs(0x8) == 3, "Find First Bit Set Failure.");
static_assert (ffs(0x10) == 4, "Find First Bit Set Failure.");
static_assert (ffs(0x20) == 5, "Find First Bit Set Failure.");
static_assert (ffs(0x40) == 6, "Find First Bit Set Failure.");
static_assert (ffs(0x80) == 7, "Find First Bit Set Failure.");
static_assert (ffs(0x100) == 8, "Find First Bit Set Failure.");
static_assert (ffs(0x200) == 9, "Find First Bit Set Failure.");
static_assert (ffs(0x400) == 10, "Find First Bit Set Failure.");
static_assert (ffs(0x800) == 11, "Find First Bit Set Failure.");
static_assert (ffs(0x1000) == 12, "Find First Bit Set Failure.");
static_assert (ffs(0x2000) == 13, "Find First Bit Set Failure.");
static_assert (ffs(0x4000) == 14, "Find First Bit Set Failure.");
static_assert (ffs(0x8000) == 15, "Find First Bit Set Failure.");
static_assert (ffs(0x10000) == 16, "Find First Bit Set Failure.");
static_assert (ffs(0x20000) == 17, "Find First Bit Set Failure.");
static_assert (ffs(0x40000) == 18, "Find First Bit Set Failure.");
static_assert (ffs(0x80000) == 19, "Find First Bit Set Failure.");
static_assert (ffs(0x100000) == 20, "Find First Bit Set Failure.");
static_assert (ffs(0x200000) == 21, "Find First Bit Set Failure.");
static_assert (ffs(0x400000) == 22, "Find First Bit Set Failure.");
static_assert (ffs(0x800000) == 23, "Find First Bit Set Failure.");
static_assert (ffs(0x1000000) == 24, "Find First Bit Set Failure.");
static_assert (ffs(0x2000000) == 25, "Find First Bit Set Failure.");
static_assert (ffs(0x4000000) == 26, "Find First Bit Set Failure.");
static_assert (ffs(0x8000000) == 27, "Find First Bit Set Failure.");
static_assert (ffs(0x10000000) == 28, "Find First Bit Set Failure.");
static_assert (ffs(0x20000000) == 29, "Find First Bit Set Failure.");
static_assert (ffs(0x40000000) == 30, "Find First Bit Set Failure.");
static_assert (ffs(0x80000000) == 31, "Find First Bit Set Failure.");