Почему Numba не улучшает эту итерацию?

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

    import numpy as np
    from numba import double
    from numba.decorators import jit, autojit

    X = np.random.random((100,2))

    def cooccurance_probability(X):
        P = X.shape[1]      
        CS = np.sum(X, axis=0)                  #Column Sums
        D = np.empty((P, P), dtype=np.float)    #Return Matrix
        for i in range(P):
            for j in range(P):
                D[i, j] = (X[:,i] * X[:,j]).sum() / max(CS[i], CS[j])
        return D 

    cooccurance_probability_numba = autojit(cooccurance_probability)

Однако я обнаружил, что производительность cooccurance_probability и cooccurance_probability_numba практически одинакова.

%timeit cooccurance_probability(X)
1 loops, best of 3: 302 ms per loop

%timeit cooccurance_probability_numba(X)
1 loops, best of 3: 307 ms per loop

Почему это? Может ли это быть связано с поэлементной операцией numpy?

Я использую в качестве примера: http://nbviewer.ipython.org/github/ellisonbg/talk-sicm2-2013/blob/master/NumbaCython.ipynb

[Примечание: я мог бы сократить время выполнения вдвое из-за симметричного характера проблемы, но это не моя главная задача]


person sanguineturtle    schedule 04.04.2014    source источник


Ответы (2)


Я предполагаю, что вы попадаете на объектный уровень вместо того, чтобы генерировать собственный код из-за вызовов sum, а это означает, что Numba не собирается значительно ускорять работу. Он просто не знает, как оптимизировать/перевести sum (на данный момент). Кроме того, обычно лучше разворачивать векторизованные операции в явные циклы с помощью Numba. Обратите внимание, что ipynb, на который вы ссылаетесь, обращается только к np.sqrt, который, как я полагаю, транслируется в машинный код и работает с элементами, а не срезами. Я бы попытался расширить сумму во внутреннем цикле как явный дополнительный цикл по элементам, а не брать срезы и использовать метод sum.

По моему опыту, Numba иногда может творить чудеса, но не ускоряет произвольный код Python. Вам нужно получить представление об ограничениях и о том, что можно эффективно оптимизировать. Также обратите внимание, что v0.11 немного отличается в этом отношении от 0.12 и 0.13 из-за серьезного рефакторинга, который Numba претерпел между этими версиями.

person JoshAdel    schedule 04.04.2014

Ниже приведено верное решение, основанное на совете Джоша. Однако кажется, что max() отлично работает в приведенной ниже реализации. Было бы здорово, если бы был список "безопасных" функций python/numpy.

Примечание. Я уменьшил размерность исходной матрицы до 100 x 200]

import numpy as np
from numba import double
from numba.decorators import jit, autojit

X = np.random.random((100,200))

def cooccurance_probability_explicit(X):
    C = X.shape[0]
    P = X.shape[1]      
    # - Column Sums - #
    CS = np.zeros((P,), dtype=np.float)
    for p in range(P):
        for c in range(C):
            CS[p] += X[c,p]
    D = np.empty((P, P), dtype=np.float)    #Return Matrix
    for i in range(P):
        for j in range(P):
            # - Compute Elemental Pairwise Sums over each Product Vector - #
            pws = 0
            for c in range(C):
                pws += (X[c,i] * X[c,j])
            D[i,j] = pws / max(CS[i], CS[j])
    return D 

cooccurance_probability_explicit_numba = autojit(cooccurance_probability_explicit)

%timeit результаты:

%timeit cooccurance_probability(X)
10 loops, best of 3: 83 ms per loop


%timeit cooccurance_probability_explicit(X)
1 loops, best of 3: 2.55s per loop

%timeit cooccurance_probability_explicit_numba(X)
100 loops, best of 3: 7.72 ms per loop

Что интересно в результатах, так это то, что явно написанная версия, выполняемая python, очень медленная из-за больших накладных расходов на проверку типов. Но прохождение через Нумбу работает волшебно. (Numba примерно в 11,5 раз быстрее, чем решение Python с использованием Numpy).


Обновление: добавлена ​​функция Cython для сравнения (спасибо moarningsun: Функция Cython с вводом матрицы переменного размера)

%load_ext cythonmagic
%%cython
import numpy as np
cimport numpy as np

def cooccurance_probability_cy(double[:,:] X):
    cdef int C, P, i, j, k
    C = X.shape[0]
    P = X.shape[1]
    cdef double pws
    cdef double [:] CS = np.sum(X, axis=0)
    cdef double [:,:] D = np.empty((P,P), dtype=np.float)

    for i in range(P):
        for j in range(P):
            pws = 0.0
            for c in range(C):
                pws += (X[c, i] * X[c, j])
            D[i,j] = pws / max(CS[i], CS[j]) 
    return D

%timeit результаты:

%timeit cooccurance_probability_cy(X)
100 loops, best of 3: 12 ms per loop
person sanguineturtle    schedule 04.04.2014
comment
Если X имеет форму [m, n], вам нужно, чтобы результат был [m, m] или [n, n]? Это отличается между вашим вопросом и вашим ответом. - person ; 06.04.2014
comment
Исходный вопрос был исправлен ... спасибо ... X имеет форму [m,n], и для вычисления совпадения я сравниваю все возможные комбинации векторов-столбцов, в результате чего получается матрица [n,n]. - person sanguineturtle; 07.04.2014
comment
Я спросил, потому что теперь ваш код работает достаточно быстро, и его разумно оптимизировать для удобства кэширования. В настоящее время вы обращаетесь к X непоследовательно, предполагая, что X является непрерывным и упорядоченным по C, что приводит к неоптимальному доступу к ОЗУ. Чтобы увидеть разницу в производительности, сделайте X квадратным массивом и засеките время функций с X и X.T в качестве аргумента. - person ; 07.04.2014
comment
вы можете ускорить cython, отключив проверки границ и обработку отрицательных индексов. - person rocksportrocker; 10.12.2018