У меня есть следующая установка:
import numpy as np
import matplotlib.pyplot as plt
import timeit
import numba
@numba.jit(nopython=True, cache=True)
def f(x):
summ = 0
for i in x:
summ += i
return summ
@numba.jit(nopython=True)
def g21(N, locs):
rvs = np.random.normal(loc=locs, scale=locs, size=N)
res = f(rvs)
return res
@numba.jit(nopython=False)
def g22(N, locs):
rvs = np.random.normal(loc=locs, scale=locs, size=N)
res = f(rvs)
return res
g22
и g21
— это одна и та же функция, только у одной из них есть nopython=True
, а у другой nopython=False
Теперь я даю им ввод. Если locs
является скаляром, то нумба должна быть в состоянии скомпилировать все, поскольку они поддерживают numpy.random.normal()
с этой сигнатурой. Однако, если locs
является массивом, numba не поддерживает эту подпись и должна вернуться к интерпретатору python.
Сначала я запускаю это, чтобы скомпилировать функции
N = 10_000
g22(N, 3)
g22(N, np.linspace(0,1,N))
g21(N, 3)
# g21(N, np.linspace(0,1,N)) # returns an error
Теперь я запускаю сравнение скорости
%timeit g21(N, 3)
%timeit g22(N, 3)
%timeit g22(N, np.linspace(0,1,N))
который возвращает
274 µs ± 3.43 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
270 µs ± 5.38 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
421 µs ± 54.3 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Имеет смысл, что g22(N, np.linspace(0,1,N)
самый медленный, поскольку он возвращается к интерпретатору Python.
Однако я не понимаю, что g21(N, 3)
примерно такая же скорость, как g22(N, 3)
, хотя у одного есть nopython=True
, а у другого нет.
Но у g22(N,3)
есть большое преимущество в том, что он может принимать другой аргумент, а именно g22(N, np.linspace(0,1,N))
, поэтому он более универсален, однако в то же время нет штрафа за скорость при наличии nopython=False
в этом случае какой смысл использовать nopython=True
, если функция с nopython=False
достигает той же скорости?
в каком конкретном случае nopython=True
лучше, чем nopython=False
?
На самом деле это может быть полезно. Я не рассматривал это, поскольку по своей сути цикл for с генерацией RV кажется менее эффективным, чем генерация их всех сразу, однако при использовании numba может быть не так много накладных расходов, поэтому это стоит учитывать. Спасибо, я попробую это!
Я тестирую это. Для 10 000 значений это примерно в два раза быстрее, чем реализация Numpy. Из-за более высоких статических накладных расходов разница становится больше, если количество значений уменьшается. Внутри Numpy, скорее всего, делает то же самое в скомпилированном коде. Не бойтесь циклов в скомпилированном коде;)
Ну, я продолжил использовать nopython=False
, и кажется, что это вызывает немного больше компиляций. Это не очень научный анализ, но кажется, что моя функция иногда перекомпилируется, когда я изменяю различные параметры, тогда как с nopython=True
она никогда не перекомпилировалась после компиляции. Так что, похоже, это разность потенциалов.
Насколько мне известно, на перекомпиляцию не влияет использование режима nopython. Однако обратите внимание, что вы можете указать подпись функции, чтобы с нетерпением ее строить. Для получения дополнительной информации об этом, пожалуйста, прочитайте: numba.readthedocs.io/en/stable/user/… . Также обратите внимание, что вы можете предоставить несколько подписей.
Вы правы, я вернулся к nopython=True
и получил такое же количество перекомпиляций, так что это был ложный флаг. Спасибо за комментарий
- in this case, what is the use of using nopython=True, if a function with nopython=False achieves same speed?
- in which specific case is nopython=True better than nopython=False?
документация указывает:
Numba has two compilation modes: nopython mode and object mode. The former produces much faster code, but has limitations that can force Numba to fall back to the latter. To prevent Numba from falling back, and instead raise an error, pass
nopython=True
.
Обратите внимание, что Numba попытается скомпилировать код в родной бинарник в оба режима. Однако nopython
выдает ошибку, когда это невозможно, в то время как другой выдает предупреждение и вызывает использование резервного кода.
Для некоторых приложений производительность может быть критична, и поэтому вы действительно не хотите, чтобы вызывался резервный код. Например, это относится к высокопроизводительные приложения. В этом случае лучше иметь ошибку, чем иметь код, который работает несколько дней, а не несколько минут на дорогой машине (например, суперкомпьютере или вычислительном сервере). Использование другой версии Numba может привести к молча вызвать откат на некоторых машинах из-за неподдерживаемой функции. Я лично всегда использую режим nopython
, чтобы предотвратить такие случаи (поскольку резервный код, как правило, слишком медленный, чтобы быть полезным), и я считаю объектный режим немного бесполезным. Дело короткое, nopython
предлагает более сильные гарантии производительности.
Большое спасибо. Я не рассматривал аспект желания быть абсолютно уверенным, что код скомпилирован. В этом смысле действительно имеет смысл использовать nopython=True
. Это имеет смысл.
Не ответ на ваш вопрос, но, если это легко возможно, может иметь смысл просто реализовать функцию, используя поддерживаемые функции. например.
res=0; for i in range(N):; res += np.random.normal(loc=locs[i], scale=locs[i]);
. Для примеров, использующих случайные числа, также имеет смысл вызвать np.random.seed(0) в функции, чтобы получить те же значения.