Почему log2 и log1p работают намного быстрее, чем log и log10?
Во время игры этот вопрос я заметил, что я не мог объяснить относительную производительность np.log2
, np.log
и np.log10
:
In [1]: %%timeit x = np.random.rand(100000)
....: np.log2(x)
....:
1000 loops, best of 3: 1.31 ms per loop
In [2]: %%timeit x = np.random.rand(100000)
np.log(x)
....:
100 loops, best of 3: 3.64 ms per loop
In [3]: %%timeit x = np.random.rand(100000)
np.log10(x)
....:
100 loops, best of 3: 3.93 ms per loop
np.log2
примерно в 3 раза быстрее, чем np.log
и np.log10
. Возможно, еще более интуитивно, np.log1p(x)
, который вычисляет ln (x + 1), находится на одном уровне с np.log2
:
In [4]: %%timeit x = np.random.rand(100000)
np.log1p(x)
....:
1000 loops, best of 3: 1.46 ms per loop
Я получил почти одинаковые тайминги в numpy v1.10.1 и v1.8.2.
Есть ли интуитивное объяснение этих несоответствий в производительности во время выполнения?
Ответы
Ответ 1
Это всего лишь примечание, но больше, чем комментарий. По-видимому, это связано с вашей конкретной установкой:
import numpy as np
import numexpr as ne
x = np.random.rand(100000)
Я получаю одинаковые тайминги с numpy 1.10 из conda и версию, скомпилированную с icc:
%timeit np.log2(x)
1000 loops, best of 3: 1.24 ms per loop
%timeit np.log(x)
1000 loops, best of 3: 1.28 ms per loop
Я подумал, что у него может быть что-то с захватом пакета MML VML, но выглядит так: нет:
%timeit ne.evaluate('log(x)')
1000 loops, best of 3: 218 µs per loop
Похоже, ваша установка numpy захватывает реализацию log/log2 из двух разных мест, которые являются нечетными.
Ответ 2
Я нашел ваш вопрос чрезвычайно интересным, поэтому я потратил несколько часов на то, чтобы сделать немного ресерах; Я думаю, что нашел объяснение разницы в производительности , поскольку это применимо к целям (спасибо Matteo Italia за вашу заметку) - Непонятно как это рассуждение может быть расширено до плавания:
Компьютеры используют базу 2. Согласно статьям, связанным в ссылке, вычисление log2 представляет собой процесс с четырьмя процессорными циклами - для log10 требуется умножить log2 (val) на 1/log2 (10), который добавляет еще 5 циклов.
Поиск log2 - это вопрос поиск индекса наименее значимого бита значения. (видео примерно на 23-й минуте).
бит-хаки: Найти целочисленную базу данных 10 целых чисел
бит-хаки: Найти базу данных 2 из N-разрядного целого числа в O (lg (N))
Целочисленная базовая база 10 вычисляется сначала с использованием одной из методы, описанные выше для нахождения базы логов 2. В силу соотношения log10 (v) = log2 (v)/log2 (10), нам нужно умножить его на 1/log2 (10), который составляет приблизительно 1233/4096, или 1233, за которым следует правая смена 12. Добавление одного из них необходимо, потому что IntegerLogBase2 округляется. Наконец, поскольку значение t является только приближением, которое может быть отключено на один, точное значение получается путем вычитания результата v < PowersOf10 [т].
Этот метод выполняет еще 6 операций, чем IntegerLogBase2. Это может быть ускоряется (на машинах с быстрым доступом к памяти), изменяя журнал base 2 table-lookup выше, чтобы записи содержали то, что вычисленный для t (т.е. pre-add, -mulitply и -shift). Делать это потребуется всего 9 операций для поиска базы 10 базы данных, предполагая, что использовались 4 таблицы (по одному для каждого байта v).
Примечание: использование методов поиска последовательностей DeBruijn и методов смещения бит для вычисления log2 в этом MIT video: Lec 2 | MIT 6.172 Performance Engineering of Software Systems, осень 2010 г. (видео на отметке 36 минут).
Обратите внимание, что это сообщение StackOverflow, которое демонстрирует метод, позволяющий эффективно выполнять вычисления log2 по-настоящему перекрестно с платформой С++
Предостережение: я не проверял исходный код numpy, чтобы убедиться, что он действительно реализует подобные методы, но было бы удивительно, что это не так. Фактически, из комментариев в сообщении OP, Fermion Portal, были отмечены:
На самом деле numpy использует math.h от glibc, вы увидите ту же разницу в C/С++, если вы используете math.h/cmath.h. Вы можете найти красиво прокомментированный исходный код для двух функций, например. ic.unicamp.br/~islene/2s2008-mo806/libc/sysdeps/ieee754/dbl-64/... и ic.unicamp.br/~islene/2s2008-mo806/libc/sysdeps/ieee754/dbl-64/... - Fermion Portal [9]
Ответ 3
Отказ от ответственности: я не являюсь ни авторитетным, ни официальным источником.
Я почти уверен, что любая реализация журнала для базовой функции e может быть выполнена так же быстро, как функция log2, потому что для преобразования одного в другое вам требуется одно деление на константу. Это, конечно, предполагает, что операция с одним делением является крошечной частью других вычислений; который в точных реализациях логарифмов истинен.
В большинстве случаев numpy использует math.h
от glibc
, вы увидите ту же самую разницу в C/С++, если используете math.h
/cmath.h
. В комментариях некоторые люди наблюдают одинаковые скорости для np.log
и np.log2
; Я подозреваю, что это может происходить из разных сборок/платформ.
Вы можете найти хорошо прокомментированный исходный код для двух функций в файлах e_log.c
, e_log2.c
, e_logf.c
, e_log2f.c
в подкаталогах dbl-64/
и flt-32/
это репозиторий GitHub.
Для двойной точности в glibc
функция log
реализует совершенно другой алгоритм (по сравнению с log2
) от IBM с ~ 2001 года, который был включен в их библиотеку libultim
. В то время как log2
принадлежит Sun Microsystems с 1993 года. Просто глядя на код, можно увидеть два разных приближения. Напротив, для единственной точности обе функции log
и log2
одинаковы, кроме деления на ln2
в случае log2
, следовательно, с той же скоростью.
Для получения более подробной информации об основных алгоритмах альтернативы и обсуждения, которые следует включить в glibc
в будущем, см. здесь здесь.