入力と出力が64ビット整数である(long int) ceiling(log_2(i))
を計算する高速な方法は何ですか?符号付きまたは符号なし整数の解決策は受け入れられます。最善の方法は、見つかったものに似た少しいじくる方法になると思います here 私自身ではなく、すでに十分にテストされているものを使用したいと思います。一般的な解決策は、すべての正の値に対して機能します。
たとえば、2、3、4、5、6、7、8の値は1、2、2、3、3、3、3です。
編集:これまでのところ、最良のルートは、任意の数の高速の既存のビットハックまたはレジスタメソッドを使用して整数/フロアログベース2(MSBの位置)を計算し、入力がべき乗でない場合は1を追加することです二。 2のべき乗の高速ビットごとのチェックは(n&(n-1))
です。
編集2:整数対数と先行ゼロ法の優れた情報源は、ヘンリーS.ウォーレンによる Hacker's Delight のセクション5-3と11-4です。これは私が見つけた最も完全な治療法です。
編集3:この手法は有望に見えます: https://stackoverflow.com/a/51351885/365478
このアルゴリズムはすでに投稿されていますが、次の実装は非常にコンパクトで、ブランチのないコードに最適化されます。
int ceil_log2(unsigned long long x)
{
static const unsigned long long t[6] = {
0xFFFFFFFF00000000ull,
0x00000000FFFF0000ull,
0x000000000000FF00ull,
0x00000000000000F0ull,
0x000000000000000Cull,
0x0000000000000002ull
};
int y = (((x & (x - 1)) == 0) ? 0 : 1);
int j = 32;
int i;
for (i = 0; i < 6; i++) {
int k = (((x & t[i]) == 0) ? 0 : j);
y += k;
x >>= k;
j >>= 1;
}
return y;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf("%d\n", ceil_log2(atol(argv[1])));
return 0;
}
自分自身をgccに制限できる場合は、先行ゼロビットの数を返す組み込み関数のセットがあり、少しの作業で必要なことを行うために使用できます。
int __builtin_clz (unsigned int x)
int __builtin_clzl (unsigned long)
int __builtin_clzll (unsigned long long)
Windowsで64ビットプロセッサ用にコンパイルしている場合、これでうまくいくと思います。 _BitScanReverse64は組み込み関数です。
#include <intrin.h>
__int64 log2ceil( __int64 x )
{
unsigned long index;
if ( !_BitScanReverse64( &index, x ) )
return -1LL; //dummy return value for x==0
// add 1 if x is NOT a power of 2 (to do the ceil)
return index + (x&(x-1)?1:0);
}
32ビットの場合、_BitScanReverseを1回または2回呼び出して、_BitScanReverse64をエミュレートできます。 xの上位32ビット((long *)&x)[1]をチェックし、必要に応じて下位32ビット((long *)&x)[0]をチェックします。
#include "stdafx.h"
#include "assert.h"
int getpos(unsigned __int64 value)
{
if (!value)
{
return -1; // no bits set
}
int pos = 0;
if (value & (value - 1ULL))
{
pos = 1;
}
if (value & 0xFFFFFFFF00000000ULL)
{
pos += 32;
value = value >> 32;
}
if (value & 0x00000000FFFF0000ULL)
{
pos += 16;
value = value >> 16;
}
if (value & 0x000000000000FF00ULL)
{
pos += 8;
value = value >> 8;
}
if (value & 0x00000000000000F0ULL)
{
pos += 4;
value = value >> 4;
}
if (value & 0x000000000000000CULL)
{
pos += 2;
value = value >> 2;
}
if (value & 0x0000000000000002ULL)
{
pos += 1;
value = value >> 1;
}
return pos;
}
int _tmain(int argc, _TCHAR* argv[])
{
assert(getpos(0ULL) == -1); // None bits set, return -1.
assert(getpos(1ULL) == 0);
assert(getpos(2ULL) == 1);
assert(getpos(3ULL) == 2);
assert(getpos(4ULL) == 2);
for (int k = 0; k < 64; ++k)
{
int pos = getpos(1ULL << k);
assert(pos == k);
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) - 1);
assert(pos == (k < 2 ? k - 1 : k) );
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) | 1);
assert(pos == (k < 1 ? k : k + 1) );
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) + 1);
assert(pos == k + 1);
}
return 0;
}
@egosysで言及されているgccビルトインを使用すると、いくつかの便利なマクロを作成できます。素早いラフな床(log2(x))の計算には、以下を使用できます。
#define FAST_LOG2(x) (sizeof(unsigned long)*8 - 1 - __builtin_clzl((unsigned long)(x)))
同様のceil(log2(x))の場合、次を使用します。
#define FAST_LOG2_UP(x) (((x) - (1 << FAST_LOG2(x))) ? FAST_LOG2(x) + 1 : FAST_LOG2(x))
後者は、組み込み関数への二重呼び出しを回避するために、より多くのgcc特性を使用してさらに最適化できますが、ここでそれが必要かどうかはわかりません。
次のコードスニペットは、@ dgobbiなどのプレーンCメソッドを拡張して、サポートされているコンパイラ(Clang)を使用してコンパイルするときにコンパイラ組み込み関数を使用する、安全で移植可能な方法です。これをメソッドの一番上に配置すると、メソッドは組み込みが使用可能なときにそれを使用します。組み込みが利用できない場合、メソッドは標準のCコードにフォールバックします。
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if __has_builtin(__builtin_clzll) //use compiler if possible
return ((sizeof(unsigned long long) * 8 - 1) - __builtin_clzll(x)) + (!!(x & (x - 1)));
#endif
私が知っている最速のアプローチは、以下に示すlg_down()
のように切り上げのケースを処理するために、前と後の入力値の無条件調整を組み合わせて切り捨てる高速_log2
_を使用します。
_/* base-2 logarithm, rounding down */
static inline uint64_t lg_down(uint64_t x) {
return 63U - __builtin_clzl(x);
}
/* base-2 logarithm, rounding up */
static inline uint64_t lg_up(uint64_t x) {
return lg_down(x - 1) + 1;
}
_
基本的に、切り捨てられた結果に1を追加することは、2の正確な累乗を除くすべての値ですでに正しいです(その場合、floor
とceil
アプローチは同じ答えを返す必要があるため)。入力値から1を減算してそのケースを処理し(他のケースの回答は変更しません)、1を結果に追加するのに十分です。
これは通常、2のべき乗を明示的にチェックして値を調整するアプローチよりもわずかに高速です(たとえば、!!(x & (x - 1))
項を追加する)。これは、比較や条件付き演算や分岐を回避し、インライン化の際に単純になりやすく、ベクトル化などの影響を受けやすくなります。
これは、clang/icc/gccビルトイン___builtin_clzl
_を使用するほとんどのCPUが提供する「カウントリーディングビット」機能に依存しますが、他のプラットフォームも同様の機能を提供します(たとえば、Visual StudioのBitScanReverse
組み込み関数)。
残念ながら、これはlog(1)
の誤った答えを返します。これは、gccドキュメントに基づく未定義の動作である__builtin_clzl(0)
につながるためです。もちろん、一般的な「カウントリーディングゼロ」関数はゼロでの動作を完全に定義していますが、gcc組み込み関数はこのように定義されています。これは、BMI ISA拡張の前に、 bsr
命令 を使用すると、それ自体は未定義の動作をします。
lzcnt
組み込み関数を直接使用してlzcnt
命令があることがわかっている場合は、これを回避できます。 x86以外のほとんどのプラットフォームは、最初にbsr
ミスを経験したことがなく、「カウントリーディングゼロ」命令がある場合はそれにアクセスするためのメソッドもおそらく提供しています。
true最速のソリューション:
63エントリのバイナリ検索ツリー。これらは、0から63までの2のべき乗です。ツリーを作成するための1回限りの生成関数。葉は累乗の対数2を表します(基本的に、1〜63の数字)。
答えを見つけるには、ツリーに数値を入力し、アイテムより大きいリーフノードに移動します。葉ノードが完全に等しい場合、結果は葉の値になります。それ以外の場合、結果はリーフ値+ 1です。
複雑さはO(6)で修正されています。
整数出力で整数(64ビットまたはその他のビット)の対数の底2を見つけることは、設定されている最上位ビットを見つけることと同じです。どうして?対数の底2は、その数を2で除算して1に到達できる回数なので、.
設定されているMSBを見つける1つの方法は、0になるまで毎回1ずつ右にビットシフトすることです。別のより効率的な方法は、ビットマスクを介して何らかのバイナリ検索を行うことです。
Ceil部分は、MSB以外のビットが設定されているかどうかを確認することで簡単に解決できます。
執筆時点でのx86-64の最速の方法と、引数が<2 ^ 63、フルレンジを気にする場合は、以下を参照してください。
他の回答の質の低さに驚いています。床を取得する方法を教えてくれますが、非常に高価な方法で(条件文などを使用して)床を天井に変換します。
___builtin_clzll
_を使用して、対数のフロアをすばやく取得できる場合、フロアは次のように非常に簡単に取得できます。
_unsigned long long log2Floor(unsigned long long x) {
return 63 - __builtin_clzll(x);
}
unsigned long long log2Ceiling(unsigned long long x) {
return log2Floor(2*x - 1);
}
_
これは、結果に1を追加するため機能しますxが正確に2の累乗でない場合。
次のような上限の別の実装については、x86-64アセンブラの違い コンパイラエクスプローラで を参照してください。
_auto log2CeilingDumb(unsigned long x) {
return log2Floor(x) + (!!(x & (x - 1)));
}
_
与える:
_log2Floor(unsigned long): # @log2Floor(unsigned long)
bsr rax, rdi
ret
log2CeilingDumb(unsigned long): # @log2CeilingDumb(unsigned long)
bsr rax, rdi
lea rcx, [rdi - 1]
and rcx, rdi
cmp rcx, 1
sbb eax, -1
ret
log2Ceiling(unsigned long): # @log2Ceiling(unsigned long)
lea rax, [rdi + rdi]
add rax, -1
bsr rax, rax
ret
_
全範囲については、以前の回答:return log2Floor(x - 1) + 1
にあります。これは、x86-64では4つの命令を使用するため、上記の3つと比較して大幅に遅くなります。
使用可能な80ビットまたは128ビットの浮動小数点数がある場合は、その型にキャストしてから、指数ビットを読み取ります。このリンクには、詳細(52ビットまでの整数)と他のいくつかの方法があります。
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float
また、ffmpegソースを確認してください。私は彼らが非常に高速なアルゴリズムを持っていることを知っています。より大きなサイズに直接拡張できない場合でも、if (x>INT32_MAX) return fastlog2(x>>32)+32; else return fastlog2(x);
のようなことを簡単に行うことができます
私は64ビットの「最高ビット」のいくつかの実装をベンチマークしました。最も「分岐のない」コードは実際には最速ではありません。
これは私の_highest-bit.c
_ソースファイルです。
_int highest_bit_unrolled(unsigned long long n)
{
if (n & 0xFFFFFFFF00000000) {
if (n & 0xFFFF000000000000) {
if (n & 0xFF00000000000000) {
if (n & 0xF000000000000000) {
if (n & 0xC000000000000000)
return (n & 0x8000000000000000) ? 64 : 63;
else
return (n & 0x2000000000000000) ? 62 : 61;
} else {
if (n & 0x0C00000000000000)
return (n & 0x0800000000000000) ? 60 : 59;
else
return (n & 0x0200000000000000) ? 58 : 57;
}
} else {
if (n & 0x00F0000000000000) {
if (n & 0x00C0000000000000)
return (n & 0x0080000000000000) ? 56 : 55;
else
return (n & 0x0020000000000000) ? 54 : 53;
} else {
if (n & 0x000C000000000000)
return (n & 0x0008000000000000) ? 52 : 51;
else
return (n & 0x0002000000000000) ? 50 : 49;
}
}
} else {
if (n & 0x0000FF0000000000) {
if (n & 0x0000F00000000000) {
if (n & 0x0000C00000000000)
return (n & 0x0000800000000000) ? 48 : 47;
else
return (n & 0x0000200000000000) ? 46 : 45;
} else {
if (n & 0x00000C0000000000)
return (n & 0x0000080000000000) ? 44 : 43;
else
return (n & 0x0000020000000000) ? 42 : 41;
}
} else {
if (n & 0x000000F000000000) {
if (n & 0x000000C000000000)
return (n & 0x0000008000000000) ? 40 : 39;
else
return (n & 0x0000002000000000) ? 38 : 37;
} else {
if (n & 0x0000000C00000000)
return (n & 0x0000000800000000) ? 36 : 35;
else
return (n & 0x0000000200000000) ? 34 : 33;
}
}
}
} else {
if (n & 0x00000000FFFF0000) {
if (n & 0x00000000FF000000) {
if (n & 0x00000000F0000000) {
if (n & 0x00000000C0000000)
return (n & 0x0000000080000000) ? 32 : 31;
else
return (n & 0x0000000020000000) ? 30 : 29;
} else {
if (n & 0x000000000C000000)
return (n & 0x0000000008000000) ? 28 : 27;
else
return (n & 0x0000000002000000) ? 26 : 25;
}
} else {
if (n & 0x0000000000F00000) {
if (n & 0x0000000000C00000)
return (n & 0x0000000000800000) ? 24 : 23;
else
return (n & 0x0000000000200000) ? 22 : 21;
} else {
if (n & 0x00000000000C0000)
return (n & 0x0000000000080000) ? 20 : 19;
else
return (n & 0x0000000000020000) ? 18 : 17;
}
}
} else {
if (n & 0x000000000000FF00) {
if (n & 0x000000000000F000) {
if (n & 0x000000000000C000)
return (n & 0x0000000000008000) ? 16 : 15;
else
return (n & 0x0000000000002000) ? 14 : 13;
} else {
if (n & 0x0000000000000C00)
return (n & 0x0000000000000800) ? 12 : 11;
else
return (n & 0x0000000000000200) ? 10 : 9;
}
} else {
if (n & 0x00000000000000F0) {
if (n & 0x00000000000000C0)
return (n & 0x0000000000000080) ? 8 : 7;
else
return (n & 0x0000000000000020) ? 6 : 5;
} else {
if (n & 0x000000000000000C)
return (n & 0x0000000000000008) ? 4 : 3;
else
return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
}
}
}
}
}
int highest_bit_bs(unsigned long long n)
{
const unsigned long long mask[] = {
0x000000007FFFFFFF,
0x000000000000FFFF,
0x00000000000000FF,
0x000000000000000F,
0x0000000000000003,
0x0000000000000001
};
int hi = 64;
int lo = 0;
int i = 0;
if (n == 0)
return 0;
for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
int mi = lo + (hi - lo) / 2;
if ((n >> mi) != 0)
lo = mi;
else if ((n & (mask[i] << lo)) != 0)
hi = mi;
}
return lo + 1;
}
int highest_bit_shift(unsigned long long n)
{
int i = 0;
for (; n; n >>= 1, i++)
; /* empty */
return i;
}
static int count_ones(unsigned long long d)
{
d = ((d & 0xAAAAAAAAAAAAAAAA) >> 1) + (d & 0x5555555555555555);
d = ((d & 0xCCCCCCCCCCCCCCCC) >> 2) + (d & 0x3333333333333333);
d = ((d & 0xF0F0F0F0F0F0F0F0) >> 4) + (d & 0x0F0F0F0F0F0F0F0F);
d = ((d & 0xFF00FF00FF00FF00) >> 8) + (d & 0x00FF00FF00FF00FF);
d = ((d & 0xFFFF0000FFFF0000) >> 16) + (d & 0x0000FFFF0000FFFF);
d = ((d & 0xFFFFFFFF00000000) >> 32) + (d & 0x00000000FFFFFFFF);
return d;
}
int highest_bit_parallel(unsigned long long n)
{
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n |= n >> 32;
return count_ones(n);
}
int highest_bit_so(unsigned long long x)
{
static const unsigned long long t[6] = {
0xFFFFFFFF00000000ull,
0x00000000FFFF0000ull,
0x000000000000FF00ull,
0x00000000000000F0ull,
0x000000000000000Cull,
0x0000000000000002ull
};
int y = (((x & (x - 1)) == 0) ? 0 : 1);
int j = 32;
int i;
for (i = 0; i < 6; i++) {
int k = (((x & t[i]) == 0) ? 0 : j);
y += k;
x >>= k;
j >>= 1;
}
return y;
}
int highest_bit_so2(unsigned long long value)
{
int pos = 0;
if (value & (value - 1ULL))
{
pos = 1;
}
if (value & 0xFFFFFFFF00000000ULL)
{
pos += 32;
value = value >> 32;
}
if (value & 0x00000000FFFF0000ULL)
{
pos += 16;
value = value >> 16;
}
if (value & 0x000000000000FF00ULL)
{
pos += 8;
value = value >> 8;
}
if (value & 0x00000000000000F0ULL)
{
pos += 4;
value = value >> 4;
}
if (value & 0x000000000000000CULL)
{
pos += 2;
value = value >> 2;
}
if (value & 0x0000000000000002ULL)
{
pos += 1;
value = value >> 1;
}
return pos;
}
_
これは_highest-bit.h
_です:
_int highest_bit_unrolled(unsigned long long n);
int highest_bit_bs(unsigned long long n);
int highest_bit_shift(unsigned long long n);
int highest_bit_parallel(unsigned long long n);
int highest_bit_so(unsigned long long n);
int highest_bit_so2(unsigned long long n);
_
そしてメインプログラム(すべてのコピーと貼り付けについて申し訳ありません):
_#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "highest-bit.h"
double timedelta(clock_t start, clock_t end)
{
return (end - start)*1.0/CLOCKS_PER_SEC;
}
int main(int argc, char **argv)
{
int i;
volatile unsigned long long v;
clock_t start, end;
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_unrolled(v);
}
end = clock();
printf("highest_bit_unrolled = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_parallel(v);
}
end = clock();
printf("highest_bit_parallel = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_bs(v);
}
end = clock();
printf("highest_bit_bs = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_shift(v);
}
end = clock();
printf("highest_bit_shift = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_so(v);
}
end = clock();
printf("highest_bit_so = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_so2(v);
}
end = clock();
printf("highest_bit_so2 = %6.3fs\n", timedelta(start, end));
return 0;
}
_
私は、この新旧のさまざまなIntel x86ボックスを試しました。
_highest_bit_unrolled
_(アンロールされたバイナリ検索)は、_highest_bit_parallel
_(分岐のないビット操作)よりも一貫して大幅に高速です。これは_highest_bit_bs
_(バイナリ検索ループ)よりも高速で、次に_highest_bit_shift
_(単純なシフトおよびカウントループ)よりも高速です。
_highest_bit_unrolled
_も、受け入れられたSO回答(_highest_bit_so
_)および別の回答(_highest_bit_so2
_)で指定されたもの)よりも高速です。
ベンチマークは、連続するビットをカバーする1ビットマスクを循環します。これは、展開されたバイナリ検索で分岐予測を無効にすることです。これは現実的です。現実のプログラムでは、入力ケースがビット位置の局所性を示す可能性は低いです。
これは古いIntel(R) Core(TM)2 Duo CPU E4500 @ 2.20GHz
の結果です:
_$ ./highest-bit
highest_bit_unrolled = 6.090s
highest_bit_parallel = 9.260s
highest_bit_bs = 19.910s
highest_bit_shift = 21.130s
highest_bit_so = 8.230s
highest_bit_so2 = 6.960s
_
新しいモデルIntel(R) Core(TM) i7-6700K CPU @ 4.00GHz
:
_highest_bit_unrolled = 1.555s
highest_bit_parallel = 3.420s
highest_bit_bs = 6.486s
highest_bit_shift = 9.505s
highest_bit_so = 4.127s
highest_bit_so2 = 1.645s
_
新しいハードウェアでは、_highest_bit_so2
_は新しいハードウェアの_highest_bit_unrolled
_に近づきます。順序はまったく同じではありません。現在_highest_bit_so
_は本当に遅れており、_highest_bit_parallel
_よりも低速です。
最速の__highest_bit_unrolled
_には、最も多くの分岐を持つ最も多くのコードが含まれます。独自の専用コードを使用して、さまざまな条件のセットが到達するすべての戻り値。
「すべての分岐を回避する」という直感は(分岐の予測ミスに関する心配のため)、常に正しいとは限りません。現代の(そしてもはやそれほど現代的ではない)プロセッサは、分岐によって妨げられないようにかなりの狡猾さが含まれています。
追伸_highest_bit_unrolled
_は 2011年12月 でTXR言語で導入されました(デバッグ以降、誤りがありました)。
最近、分岐のないより良い、よりコンパクトなコードが速くないのではないかと思い始めました。
私は結果に多少驚いています。
間違いなく、コードは実際には_#ifdef
_- ing for GNU Cおよびいくつかのコンパイラプリミティブを使用する必要がありますが、移植性に関しては、そのバージョンはそのままです。
ナイーブ線形検索は、均等に分散された整数のオプションである場合があります。これは、(任意の整数サイズに対して)平均で2未満の比較が必要なためです。
/* between 1 and 64 comparisons, ~2 on average */
#define u64_size(c) ( \
0x8000000000000000 < (c) ? 64 \
: 0x4000000000000000 < (c) ? 63 \
: 0x2000000000000000 < (c) ? 62 \
: 0x1000000000000000 < (c) ? 61 \
...
: 0x0000000000000002 < (c) ? 2 \
: 0x0000000000000001 < (c) ? 1 \
: 0 \
)
以下のコードはより単純で、入力x> = 1である限り機能します。入力clog2(0)は未定義の応答を取得します(log(0)は無限大なので理にかなっています...)エラーチェックを追加できます( x == 0)必要な場合:
unsigned int clog2 (unsigned int x)
{
unsigned int result = 0;
--x;
while (x > 0) {
++result;
x >>= 1;
}
return result;
}
ちなみに、log2のfloorのコードは似ています(ここでも、x> = 1と仮定しています)。
unsigned int flog2 (unsigned int x)
{
unsigned int result = 0;
while (x > 1) {
++result;
x >>= 1;
}
return result;
}