Дальнейшая оптимизация простого кода Cython - PullRequest
0 голосов
/ 13 ноября 2018

У меня есть функция, написанная на Cython, которая вычисляет определенную меру корреляции (корреляция расстояния) с помощью двойного цикла for:

%%cython -a
import numpy as np

def distances_Matrix(X):
    return np.array([[np.linalg.norm(xi-xj) for xi in X] for xj in X])

def c_dCov(double[:, :] a, double[:, :] b, int n):
    cdef int i
    cdef int j
    cdef double U       =  0
    cdef double W1      =  n/(n-1)
    cdef double W2      =  2/(n-2)
    cdef double[:] a_M  =  np.mean(a,axis=1)
    cdef double    a_   =  np.mean(a)
    cdef double[:] b_M  =  np.mean(b,axis=1)
    cdef double    b_   =  np.mean(b)

    for i in range(n):
        for j in range(n):
            if i != j:
                U = U + (a[i][j] + W1*(-a_M[i]-a_M[j]+a_)) * (b[i][j] +   W1*(-b_M[i]-b_M[j]+b_))
            else:
                U = U - W2*(W1**2)*(a_M[i] - a_) * (b_M[i] - b_)
    return U/(n*(n-3))

def c_dCor(X,Y):
    n     =  len(X)
    a     =  distances_Matrix(X)
    b     =  distances_Matrix(Y)
    V_XX  =  c_dCov(a,a,n) 
    V_YY  =  c_dCov(b,b,n)
    V_XY  =  c_dCov(a,b,n)
    return V_XY/np.sqrt(V_XX*V_YY)

Когда я компилирую этот фрагмент кода, я получаю следующий отчет по оптимизациикомпилятором:

enter image description here

Строка 23 все еще довольно желтая, что указывает на значительные взаимодействия с питоном, как я могу сделать эту линию еще более оптимизированной?.

Операции, выполняемые в этой строке, довольно тривиальны, это просто продукты и суммы, поскольку я указал типы каждого массива и переменной, используемые в этой функции, почему я получаю такую ​​плохую производительность в этой строке?

Заранее спасибо.

1 Ответ

0 голосов
/ 13 ноября 2018

Краткий ответ: отключить проверку границ в функции c_dCov, добавив следующий декоратор в строку прямо перед ней:

cimport cython
@cython.boundscheck(False)  # Deactivate bounds checking
def c_dCov(double[:, :] a, double[:, :] b, int n):

Кроме того, вы можете добавить директиву компилятора в начало вашего кода. Сразу после вашей магической линии Cython вы должны поставить:

%%cython -a
#cython: language_level=3, boundscheck=False

Если у вас есть файл setup.py, вы также можете отключить глобальную проверку границ:

from distutils.core import setup
from Cython.Build import cythonize

setup(
    name="foo",
    ext_modules=cythonize('foo.pyx', compiler_directives={'boundscheck': False}),
)

Независимо от того, как это было сделано, отключения проверки границ само по себе было достаточно, чтобы получить следующий отчет по оптимизации:

enter image description here

Некоторые другие оптимизации, предлагаемые документами Cython , отключают индексацию с отрицательными числами и заявляют, что ваши массивы гарантированно будут иметь непрерывный макет в памяти. С учетом всех этих оптимизаций подпись c_dCov станет:

cimport cython
@cython.boundscheck(False)  # Deactivate bounds checking
@cython.wraparound(False)   # Deactivate negative indexing.
def c_dCov(double[:, ::1] a, double[:, ::1] b, int n):

, но только @cython.boundscheck(False) было необходимо для получения лучшего отчета по оптимизации.

Теперь, когда я посмотрю поближе, хотя у вас нет этих оптимизаций в вашем фрагменте кода, у вас есть декораторы boundscheck(False) и wraparound(False) в коде в вашем отчете по оптимизации. Вы уже пробовали те, и они не работали? Какую версию Cython вы используете? Может быть, вам нужно обновление.

Объяснение

Каждый раз, когда вы обращаетесь к массиву по индексу, происходит проверка границ. Это связано с тем, что когда у вас есть массив arr формы (5,5) и вы пытаетесь получить доступ к arr[19,27], ваша программа выдаст ошибку вместо того, чтобы позволить вам получить доступ за пределами данных. Однако, ради скорости, некоторые языки не выполняют проверку границ доступа к массиву (например, C / C ++). Cython позволяет при желании отключить проверку границ для оптимизации производительности. В Cython вы можете либо отключить глобальную проверку границ для всей программы с помощью директивы компилятора boundscheck , либо для отдельной функции с декоратором @cython.boundscheck(False) .

...