Когда эффективна нумба? - PullRequest
1 голос
/ 29 марта 2019

Я знаю, что numba создает некоторые накладные расходы, а в некоторых ситуациях (не интенсивное вычисление) он становится медленнее, чем чистый питон. Но я не знаю, где провести черту. Можно ли использовать порядок сложности алгоритма, чтобы выяснить, где?

например, для добавления двух массивов (~ O (n)) короче, чем 5 в этом коде чистый python быстрее:

def sum_1(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

@numba.jit('float64[:](float64[:],float64[:])')
def sum_2(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

# try 100
a = np.linspace(1.0,2.0,5)
b = np.linspace(1.0,2.0,5)
print("pure python: ")
%timeit -o sum_1(a,b)
print("\n\n\n\npython + numba: ")
%timeit -o sum_2(a,b)

ОБНОВЛЕНИЕ: то, что я ищу, похоже на здесь :

"Общая рекомендация - выбирать разные цели для разных размеров данных и алгоритмов. Цель cpu хорошо работает для алгоритмов данных небольшого размера (примерно менее 1 КБ) и низкой интенсивности вычислений. Она имеет наименьшую нагрузку «Параллельная» цель хорошо работает для средних размеров данных (около 1 МБ). Потоковая обработка добавляет небольшую задержку. Цель «cuda» хорошо работает для больших объемов данных (около 1 МБ) и алгоритмов с высокой интенсивностью вычислений. Перенос памяти в и из графического процессора добавляет значительные накладные расходы ".

Ответы [ 3 ]

3 голосов
/ 31 марта 2019

Трудно провести черту, когда нумба вступает в силу.Однако есть несколько индикаторов, которые могут оказаться неэффективными:

  • Если вы не можете использовать jit с nopython=True - всякий раз, когда вы не можете скомпилировать его в режиме nopython, вы либо пытаетесь скомпилироватьслишком много или это не будет значительно быстрее.

  • Если вы не используете массивы - когда вы имеете дело со списками или другими типами, которые вы передаете в функцию numba (кроме другихфункции numba), numba необходимо скопировать их, что приводит к значительным накладным расходам.

  • Если уже есть функция NumPy или SciPy, которая делает это - даже если numba может быть значительно быстрее для коротких массивовэто почти всегда будет так же быстро для более длинных массивов (также вы можете легко пренебречь некоторыми общими крайними случаями, с которыми они справятся).

Есть и другая причина, по которой вы не захотите использовать numbaв случаях, когда это просто «немного» быстрее, чем в других решениях: функции Numba должны быть скомпилированы заранее или при первом вызове, в некоторых ситуацияхs компиляция будет намного медленнее, чем ваша выгода, даже если вы вызываете ее сотни раз.Также время компиляции складывается: numba медленно импортируется, а компиляция функций numba также добавляет некоторые накладные расходы.Не имеет смысла экономить несколько миллисекунд, если накладные расходы на импорт увеличились на 1-10 секунд.

Кроме того, numba сложна в установке (по крайней мере, без conda), поэтому, если вы хотите поделиться своим кодом, тогдау вас действительно «сильная зависимость».


В вашем примере отсутствует сравнение с методами NumPy и высоко оптимизированной версией чистого Python.Я добавил еще несколько функций сравнения и провел тест (используя мою библиотеку simple_benchmark):

import numpy as np
import numba as nb
from itertools import chain

def python_loop(a,b):
    result = 0.0
    for i,j in zip(a,b):
        result += (i+j)
    return result

@nb.njit
def numba_loop(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

def numpy_methods(a, b):
    return a.sum() + b.sum()

def python_sum(a, b):
    return sum(chain(a.tolist(), b.tolist()))

from simple_benchmark import benchmark, MultiArgument

arguments = {
    2**i: MultiArgument([np.zeros(2**i), np.zeros(2**i)])
    for i in range(2, 17)
}
b = benchmark([python_loop, numba_loop, numpy_methods, python_sum], arguments, warmups=[numba_loop])

%matplotlib notebook
b.plot()

enter image description here

Дафункция numba является самой быстрой для небольших массивов, однако решение NumPy будет немного быстрее для более длинных массивов.Решения Python работают медленнее, но «более быстрая» альтернатива уже значительно быстрее, чем ваше первоначальное предлагаемое решение.

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

2 голосов
/ 01 апреля 2019

Если вы точно не знаете, что является следствием явных объявлений ввода и вывода, пусть numba решит это. С вашим вводом вы можете использовать 'float64(float64[::1],float64[::1])'. (скалярный вывод, непрерывные входные массивы). Если вы вызовете явно объявленную функцию с пошаговыми вводами, она потерпит неудачу, если Numba сделает работу, она просто перекомпилируется. Без использования fastmath=True также невозможно использовать SIMD, потому что это изменяет точность результата.

Вычисление не менее 4 частичных сумм (256-битный вектор) и вычисление суммы этих частичных сумм здесь предпочтительнее (Numpy также не вычисляет наивную сумму).

Пример использования превосходной утилиты тестирования MSeiferts

import numpy as np
import numba as nb
from itertools import chain

def python_loop(a,b):
    result = 0.0
    for i,j in zip(a,b):
        result += (i+j)
    return result

@nb.njit
def numba_loop_zip(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

#Your version with suboptimal input and output (prevent njit compilation) declaration
@nb.jit('float64[:](float64[:],float64[:])')
def numba_your_func(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

@nb.njit(fastmath=True)
def numba_loop_zip_fastmath(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

@nb.njit(fastmath=True)
def numba_loop_fastmath_single(a,b):
    result = 0.0
    size=min(a.shape[0],b.shape[0])
    for i in range(size):
            result += a[i]+b[i]
    return result

@nb.njit(fastmath=True,parallel=True)
def numba_loop_fastmath_multi(a,b):
    result = 0.0
    size=min(a.shape[0],b.shape[0])
    for i in nb.prange(size):
            result += a[i]+b[i]
    return result

#just for fun... single-threaded for small arrays,
#multithreaded for larger arrays
@nb.njit(fastmath=True,parallel=True)
def numba_loop_fastmath_combined(a,b):
    result = 0.0
    size=min(a.shape[0],b.shape[0])
    if size>2*10**4:
        result=numba_loop_fastmath_multi(a,b)
    else:
        result=numba_loop_fastmath_single(a,b)
    return result

def numpy_methods(a, b):
    return a.sum() + b.sum()

def python_sum(a, b):
    return sum(chain(a.tolist(), b.tolist()))

from simple_benchmark import benchmark, MultiArgument

arguments = {
    2**i: MultiArgument([np.zeros(2**i), np.zeros(2**i)])
    for i in range(2, 19)
}
b = benchmark([python_loop, numba_loop_zip, numpy_methods,numba_your_func, python_sum,numba_loop_zip_fastmath,numba_loop_fastmath_single,numba_loop_fastmath_multi,numba_loop_fastmath_combined], arguments, warmups=[numba_loop_zip,numba_loop_zip_fastmath,numba_your_func,numba_loop_fastmath_single,numba_loop_fastmath_multi,numba_loop_fastmath_combined])

%matplotlib notebook
b.plot()

Results

Обратите внимание, что использование numba_loop_fastmath_multi или numba_loop_fastmath_combined(a,b) рекомендуется только в некоторых особых случаях. Чаще всего такая простая функция является частью другой проблемы, которая может быть более эффективно распараллелена (запуск потоков имеет некоторые накладные расходы)

1 голос
/ 29 марта 2019

Запуск этого кода приводит к ~ 6-кратному ускорению на моей машине:

@numba.autojit
def sum_2(a,b):
    result = 0.0
    for i,j in zip(a,b):
            result += (i+j)
    return result

Python: 3,31 мкс, numba: 589 нс.

Что касается вашего вопроса, я действительно думаю, что это на самом деле не связано со сложностью и, вероятно, будет зависеть в основном от вида выполняемых вами операций.С другой стороны, вы все еще можете построить сравнение python / numba, чтобы увидеть, где происходит сдвиг для данной функции.

...