Нахождение экспоненты n = 2 ** x использующий битовые операции [логарифм в основе 2 из n]

Существует ли простой путь к извлечению экспоненты от питания 2 битовых операций использования только?

Править: Хотя вопрос был первоначально о битовых операциях, поток является хорошим чтением также, если Вы задаетесь вопросом, "Что самый быстрый путь состоит в том, чтобы найти X данными Y = 2X в Python?" **

Я в настоящее время пытаюсь оптимизировать стандартную программу (тест простоты чисел Rabin-Miller), который сокращает четное количество N в формах 2**s * d. Я могу добраться 2**s часть:

two_power_s = N & -N

но я не могу найти способ извлечь просто "s" с битовой операцией. Обходные решения, которые я в настоящее время тестирую без слишком большой удовлетворенности (они являются все в значительной степени медленными):

  • использование функции логарифма
  • управление двоичным представлением 2 ** s (т.е. подсчет конечных нулей)
  • цикличное выполнение на подразделении 2, пока результат не равняется 1

Я использую Python, но ответ на этот вопрос должен быть агностиком языка, я предполагаю.

14
задан ROMANIA_engineer 13 January 2018 в 08:22
поделиться

6 ответов

Краткий ответ

Что касается python:

  • Самый быстрый метод из всех для нахождения экспоненты 2**x - это поиск в словаре, хэши которого являются степенями 2 (см. "hashlookup" в коде)
  • Самый быстрый побитовый метод - это метод под названием "unrolled_bitwise".
  • Оба предыдущих метода имеют четко определенные (но расширяемые) верхние границы. Самый быстрый метод без жестко заданных верхних пределов (который масштабируется настолько, насколько python может обрабатывать числа) - это "log_e".

Предварительные замечания

  1. Все приведенные ниже измерения скорости были получены с помощью timeit.Timer.repeat(testn, cycles) где testn был установлен на 3, а cycles был автоматически скорректирован скриптом для получения времени в диапазоне секунд (примечание: в этом механизме автокоррекции была ошибка, которая была исправлена 18/02/2010).
  2. Не все методы могут масштабироваться, поэтому я не стал тестировать все функции для различных значений степени 2
  3. Мне не удалось заставить работать некоторые из предложенных методов (функция возвращает неверный результат). У меня еще не было времени на пошаговую отладку: Я включил код (с комментариями) на случай, если кто-то обнаружит ошибку при проверке (или захочет выполнить отладку самостоятельно)

Результаты

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
5
ответ дан 1 December 2019 в 12:52
поделиться

На самом деле это комментарий к тесту производительности, опубликованному 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 неотмеченных целых чисел, это должно быть примерно так:

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 );
1
ответ дан 1 December 2019 в 12:52
поделиться

«языковой агностик» и беспокойство о производительности - в значительной степени несовместимые понятия .

В большинстве современных процессоров есть инструкция CLZ «считать ведущие нули». В GCC вы можете добраться до него с помощью __builtin_clz (x) (который также дает разумный, если не самый быстрый, код для целей, в которых отсутствует clz). Обратите внимание, что этот CLZ не определен для нуля, поэтому вам понадобится дополнительная ветка, чтобы поймать этот случай, если он имеет значение в вашем приложении.

В CELT ( 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.

6
ответ дан 1 December 2019 в 12:52
поделиться

Есть страница с большим количеством подобных трюков и хаков. Она написана для языка 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.

4
ответ дан 1 December 2019 в 12:52
поделиться

У меня также есть индекс поиска lucene, который используется несколькими клиентами, я решаю эту проблему, делая «Службу поиска Lucene» отдельной веб-службой, работающей в собственном домене приложений. Поскольку оба клиента попали в одну веб-службу для поиска или обновления индекса, я могу сделать его защищенным от потоков с блокировками на индексаторах Lucene.

Кроме того, если вы хотите сохранить его в процессе, я предлагаю использовать блокировки файлов, чтобы убедиться, что только один клиент может записать в индекс.

Чтобы получить возможность использовать новый индекс, я создаю его на стороне, а затем предлагаю службе Search Index поменяться местами для использования нового индекса путем безопасного удаления любых индексаторов в текущем индексе и переименования каталогов, Например,

  • Index.Current > Index.Old
  • Index.New > Index.Current
-121--1420991-

Я думаю, что необходимо включить проверку выполнения контракта в параметрах настройки проекта (должна быть панель «Code Contracts»...)

Для получения дополнительной информации см. пользовательскую документацию (раздел 6).

-121--4378655-

Похоже, диапазон известен. Давайте предположим, что он поднимается до 1 < < 20, просто чтобы сделать его более интересным:

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.

И просто для удовольствия можно было бы также использовать шестнадцатеричный метод на плавающих объектах, чтобы получить последовательность, который включает (в качестве его последней части) базовый 2 экспонента числа. Это немного медленно извлекается в целом, но если экспонента когда-либо будет только одна цифра, это может быть сделано достаточно просто:

def floathex(v):
    return ord(float(v).hex()[-1])-48

Это чисто для развлекательной ценности, хотя, как это было неконкурентоспособно - хотя, удивительно, все еще быстрее, чем побитовый подход.

Похоже, что использовать список - это путь.

(Этот подход не будет масштабироваться бесконечно из-за ограниченной памяти, но для того, чтобы компенсировать, что скорость выполнения не будет зависеть от max _ log2 или входных значений, в любом пути, которое вы заметите при запуске кода python. Что касается потребления памяти, то, если я правильно запомню внутренние устройства питона, таблица займет около (1 < < max _ log2) * 4 байт,потому что содержимое все небольшие целые числа, которые интерпретатор будет интернировать автоматически. Итак, когда max _ log2 равно 20, это примерно 4MB.)

1
ответ дан 1 December 2019 в 12:52
поделиться

Вы можете сделать это за время 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

Для целых чисел фиксированного размера, таблица поиска должна быть самым быстрым решением, и, вероятно, лучшим в целом.

3
ответ дан 1 December 2019 в 12:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: