Нахождение показателя N = 2* * x с помощью побитовых операций [логарифм в базе 2 из n]
есть ли простой способ извлечения экспоненты из степени 2, используя только побитовые операции?
EDIT: хотя вопрос изначально был о побитовых операциях, поток также хорошо читается, если вам интересно " каков самый быстрый способ найти X с учетом Y = 2X в Python?"* *
В настоящее время я пытаюсь оптимизировать процедуру (Rabin-Miller primality test), что снижает даже N в форм 2**s * d
. Я могу достать 2**s
участие:
two_power_s = N & -N
но я не могу найти способ, чтобы извлечь только "s" С побитовой операции. Обходные пути, которые я в настоящее время тестирую без особого удовлетворения (все они довольно медленные):
- использование функции логарифма
- управление двоичным представлением 2* * s (т. е. подсчет трейлинга нули)
- петля на деление на 2, пока результат не будет 1
Я использую Python, но ответ на этот вопрос должен быть язык агностик, я полагаю.
6 ответов
"языковой агностик" и беспокойство о производительности в значительной степени несовместимы.
большинство современных процессоров имеют инструкцию CLZ, "подсчет ведущих нулей". В GCC вы можете добраться до него с __builtin_clz(x) (который также производит разумный, если не самый быстрый, код для целей, которым не хватает clz). Обратите внимание, что эта CLZ не определена для нуля, поэтому вам понадобится дополнительная ветвь, чтобы поймать этот случай, если это имеет значение в вашем приложении.
в КЕЛЬТЕ ( http://celt-codec.org) CLZ без ветвей, который мы используем для комплиеров, не имеющих CLZ, был написан Тимоти Б. Терриберри:
int ilog(uint32 _v){
int ret;
int m;
ret=!!_v;
m=!!(_v&0xFFFF0000)<<4;
_v>>=m;
ret|=m;
m=!!(_v&0xFF00)<<3;
_v>>=m;
ret|=m;
m=!!(_v&0xF0)<<2;
_v>>=m;
ret|=m;
m=!!(_v&0xC)<<1;
_v>>=m;
ret|=m;
ret+=!!(_v&0x2);
return ret;
}
(комментарии указывают, что это было обнаружено быстрее, чем ветвящаяся версия и версия на основе таблицы поиска)
но если производительность настолько важна, вы, вероятно, не должны реализовывать эту часть своего кода в python.
короткий ответ:
что касается python:
- на самый быстрый метод из всех найти показатель 2* * x, посмотрев в словаре, хэши которого являются степенями 2 (см."hashlookup" в коде)
- на самый быстрый побитовый метод - это называется "unrolled_bitwise".
- оба предыдущих метода имеют четко определенные (но расширяемые) верхние пределы. The самый быстрый метод без жестко закодированных верхних пределов (который масштабируется, насколько python может обрабатывать числа) является "log_e".
предварительные замечания
- все измерения скорости ниже были получены через
timeit.Timer.repeat(testn, cycles)
здесьtestn
было установлено 3 иcycles
был автоматически настроен скриптом для получения времени в диапазоне секунд (Примечание: в этой автоматической настройке была ошибка механизм, который был установлен 18/02/2010). - не все методы могут масштабироваться, вот почему я не тестировал все функции для различных степеней 2
- мне не удалось заставить некоторые из предложенных методов работать (функция возвращает неверный результат). У меня еще не было tiem для пошаговой отладки: я включил код (прокомментировал) на случай, если кто-то обнаружит ошибку при проверке (или захочет выполнить отладку сами)
результаты
func (25)**
hashlookup: 0.13s 100%
lookup: 0.15s 109%
stringcount: 0.29s 220%
unrolled_bitwise: 0.36s 272%
log_e: 0.60s 450%
bitcounter: 0.64s 479%
log_2: 0.69s 515%
ilog: 0.81s 609%
bitwise: 1.10s 821%
olgn: 1.42s 1065%
func (231)**
hashlookup: 0.11s 100%
unrolled_bitwise: 0.26s 229%
log_e: 0.30s 268%
stringcount: 0.30s 270%
log_2: 0.34s 301%
ilog: 0.41s 363%
bitwise: 0.87s 778%
olgn: 1.02s 912%
bitcounter: 1.42s 1264%
func (2128)**
hashlookup: 0.01s 100%
stringcount: 0.03s 264%
log_e: 0.04s 315%
log_2: 0.04s 383%
olgn: 0.18s 1585%
bitcounter: 1.41s 12393%
func (21024)**
log_e: 0.00s 100%
log_2: 0.01s 118%
stringcount: 0.02s 354%
olgn: 0.03s 707%
bitcounter: 1.73s 37695%
код
import math, sys
def stringcount(v):
"""mac"""
return len(bin(v)) - 3
def log_2(v):
"""mac"""
return int(round(math.log(v, 2), 0)) # 2**101 generates 100.999999999
def log_e(v):
"""bp on mac"""
return int(round(math.log(v)/0.69314718055994529, 0)) # 0.69 == log(2)
def bitcounter(v):
"""John Y on mac"""
r = 0
while v > 1 :
v >>= 1
r += 1
return r
def olgn(n) :
"""outis"""
if n < 1:
return -1
low = 0
high = sys.getsizeof(n)*8 # not the best upper-bound guesstimate, but...
while True:
mid = (low+high)//2
i = n >> mid
if i == 1:
return mid
if i == 0:
high = mid-1
else:
low = mid+1
def hashlookup(v):
"""mac on brone -- limit: v < 2**131"""
# def prepareTable(max_log2=130) :
# hash_table = {}
# for p in range(1, max_log2) :
# hash_table[2**p] = p
# return hash_table
global hash_table
return hash_table[v]
def lookup(v):
"""brone -- limit: v < 2**11"""
# def prepareTable(max_log2=10) :
# log2s_table=[0]*((1<<max_log2)+1)
# for i in range(max_log2+1):
# log2s_table[1<<i]=i
# return tuple(log2s_table)
global log2s_table
return log2s_table[v]
def bitwise(v):
"""Mark Byers -- limit: v < 2**32"""
b = (0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000)
S = (1, 2, 4, 8, 16)
r = 0
for i in range(4, -1, -1) :
if (v & b[i]) :
v >>= S[i];
r |= S[i];
return r
def unrolled_bitwise(v):
"""x4u on Mark Byers -- limit: v < 2**33"""
r = 0;
if v > 0xffff :
v >>= 16
r = 16;
if v > 0x00ff :
v >>= 8
r += 8;
if v > 0x000f :
v >>= 4
r += 4;
if v > 0x0003 :
v >>= 2
r += 2;
return r + (v >> 1)
def ilog(v):
"""Gregory Maxwell - (Original code: B. Terriberry) -- limit: v < 2**32"""
ret = 1
m = (not not v & 0xFFFF0000) << 4;
v >>= m;
ret |= m;
m = (not not v & 0xFF00) << 3;
v >>= m;
ret |= m;
m = (not not v & 0xF0) << 2;
v >>= m;
ret |= m;
m = (not not v & 0xC) << 1;
v >>= m;
ret |= m;
ret += (not not v & 0x2);
return ret - 1;
# following table is equal to "return hashlookup.prepareTable()"
hash_table = {...} # numbers have been cut out to avoid cluttering the post
# following table is equal to "return lookup.prepareTable()" - cached for speed
log2s_table = (...) # numbers have been cut out to avoid cluttering the post
есть страница с большим количеством этих типов трюков и хаков. Это написано для C, но многие из них также должны работать на Python (хотя производительность, очевидно, будет отличаться). Бит, который вы хотите, это здесь и далее.
можно попробовать этой например:
register unsigned int r = 0; // result of log2(v) will go here
for (i = 4; i >= 0; i--) // unroll for speed...
{
if (v & b[i])
{
v >>= S[i];
r |= S[i];
}
}
похоже, что он может быть преобразован в Python довольно легко.
вы можете сделать это за O (lg s) время для целых чисел произвольной длины, используя binsearch.
import sys
def floorlg(n):
if n < 1:
return -1
low=0
high=sys.getsizeof(n)*8 # not the best upper-bound guesstimate, but...
while True:
mid = (low+high)//2
i = n >> mid
if i == 1:
return mid
if i == 0:
high = mid-1
else:
low = mid+1
для целых чисел фиксированного размера таблица поиска должна быть самым быстрым решением и, вероятно, лучшим в целом.
кажется, что диапазон известен. Предположим, что он поднимается до 1
max_log2=20
поэтому составьте список, который (по сути) отображает целое число в его логарифм базы 2. Следующее сделает трюк:
log2s_table=[0]*((1<<max_log2)+1)
for i in range(max_log2+1):
log2s_table[1<<i]=i
(это не делает ничего полезного для чисел, которые не являются степенями двух; постановка проблемы предполагает, что их не нужно обрабатывать. Это было бы достаточно легко исправить.)
функция для того чтобы получить логарифм очень прост и может быть легко встроен:
def table(v):
return log2s_table[v]
я не могу гарантировать, что тестовый код, который я написал, точно такой же, как тот, который используется для получения таймингов примера, но это гораздо быстрее, чем stringcount
код:
stringcount: 0.43 s.
table: 0.16 s.
поскольку все значения в таблице меньше 256, я задавался вопросом, будет ли использование строки вместо списка быстрее или, возможно,array.array
байтов, но не кости:
string: 0.25 s.
arr: 0.21 s.
С помощью dict
сделать поиск является еще одной возможностью, воспользовавшись тем, как проверяются только полномочия двух:
log2s_map=dict([(1<<x,x) for x in range(max_log2+1)])
def map(v):
return log2s_map[v]
результаты для этого были не так хороши, хотя:
map: 0.20 s.
и просто для удовольствия можно также использовать hex
метод на float-объектах, чтобы получить строку, которая включает (в качестве последней части) базовый показатель 2 числа. Это немного медленное извлечение в целом, но если показатель будет только одной цифрой, это можно сделать прямолинейно достаточно:
def floathex(v):
return ord(float(v).hex()[-1])-48
это чисто хоть для развлечения, как это было uncompetetive-хотя, удивительно, еще быстрее, чем подход побитовые.
таким образом, похоже, что использование списка-это путь.
(этот подход не будет масштабироваться бесконечно, из-за ограниченной памяти, но компенсировать это скорость выполнения не будет зависеть от max_log2
или входные значения, любым способом, который вы заметите при запуске кода python. Относительно памяти потребление, если я правильно помню свои внутренности python, таблица займет около (1<<max_log2)*4
байт, потому что содержимое-это все малые целые числа, которые интерпретатор будет интернировать автоматически. Итак, когда max_log2
это 20, это около 4 МБ.)
это фактически комментарий к тесту производительности, опубликованному mac. Я отправляю это как ответ, чтобы иметь правильное форматирование кода и отступов
mac, не могли бы вы попробовать развернутую реализацию bitseach, предложенную Марком Байерсом? Может быть, это просто доступ к массиву, который замедляет его. Теоретически этот подход должен быть быстрее других.
это будет выглядеть примерно так, хотя я не уверен, подходит ли форматирование для python, но я думаю, вы можете видеть, что он должен делать.
def bitwise(v):
r = 0;
if( v > 0xffff ) : v >>= 16; r = 16;
if( v > 0x00ff ) : v >>= 8; r += 8;
if( v > 0x000f ) : v >>= 4; r += 4;
if( v > 0x0003 ) : v >>= 2; r += 2;
return r + ( v >> 1 );
Если Python акций, отсутствие в Java из unsingned чисел он должен быть что-то вроде этого:
def bitwise(v):
r = 0;
if( v & 0xffff0000 ) : v >>>= 16; r = 16;
if( v > 0x00ff ) : v >>= 8; r += 8;
if( v > 0x000f ) : v >>= 4; r += 4;
if( v > 0x0003 ) : v >>= 2; r += 2;
return r + ( v >> 1 );