Достижение производительности Numba с Cython - PullRequest
0 голосов
/ 27 августа 2018

Обычно я могу соответствовать производительности Numba при использовании Cython. Однако в этом примере мне не удалось это сделать - Numba примерно в 4 раза быстрее, чем версия моего Cython.

Здесь Cython-версия:

%%cython -c=-march=native -c=-O3
cimport numpy as np
import numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def cy_where(double[::1] df):
    cdef int i
    cdef int n = len(df)
    cdef np.ndarray[dtype=double] output = np.empty(n, dtype=np.float64)
    for i in range(n):
        if df[i]>0.5:
            output[i] = 2.0*df[i]
        else:
            output[i] = df[i]
    return output 

А вот Numba-версия:

import numba as nb
@nb.njit
def nb_where(df):
    n = len(df)
    output = np.empty(n, dtype=np.float64)
    for i in range(n):
        if df[i]>0.5:
            output[i] = 2.0*df[i]
        else:
            output[i] = df[i]
    return output

При тестировании версия Cython находится на одном уровне с where numpy, но явно уступает Numba:

#Python3.6 + Cython 0.28.3 + gcc-7.2
import numpy
np.random.seed(0)
n = 10000000
data = np.random.random(n)

assert (cy_where(data)==nb_where(data)).all()
assert (np.where(data>0.5,2*data, data)==nb_where(data)).all()

%timeit cy_where(data)       # 179ms
%timeit nb_where(data)       # 49ms (!!)
%timeit np.where(data>0.5,2*data, data)  # 278 ms

В чем причина производительности Numba и как ее можно сопоставить при использовании Cython?


Как предлагает @ max9111, устранение шага с помощью непрерывного просмотра памяти, что не сильно повышает производительность:

@cython.boundscheck(False)
@cython.wraparound(False)
def cy_where_cont(double[::1] df):
    cdef int i
    cdef int n = len(df)
    cdef np.ndarray[dtype=double] output = np.empty(n, dtype=np.float64)
    cdef double[::1] view = output  # view as continuous!
    for i in range(n):
        if df[i]>0.5:
            view[i] = 2.0*df[i]
        else:
            view[i] = df[i]
    return output 

%timeit cy_where_cont(data)   #  165 ms

Ответы [ 2 ]

0 голосов
/ 28 октября 2018

Интересно, что компиляция исходного кода Numpy с помощью pythran с использованием clang в качестве бэкэнда дает ту же производительность, что и версия Numba.

import numpy as np
#pythran export work(float64[])

def work(df):
    return np.where(data>0.5,2*data, data)

Скомпилировано с

CXX=clang++ CC=clang pythran pythran_work.py -O3 -march=native

и тестовая сессия:

import numpy as np
np.random.seed(0)
n = 10000000
data = np.random.random(n)
import numba_work, pythran_work

%timeit numba_work.work(data)
12.7 ms ± 20 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit pythran_work.work(data)
12.7 ms ± 32.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
0 голосов
/ 02 сентября 2018

Это, кажется, полностью обусловлено оптимизацией, которую LLVM может сделать. Если я скомпилирую пример Cython с Clang, производительность между двумя примерами одинакова. Для чего это стоит, MSVC на Windows показывает сходство производительности с Numba.

$ CC=clang ipython
<... setup code>

In [7]: %timeit cy_where(data)       # 179ms
   ...: %timeit nb_where(data)       # 49ms (!!) 

30.8 ms ± 309 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
30.2 ms ± 498 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
...