Просмотр памяти Cython не быстрее, чем ndarray

У меня есть функция, написанная на регулярном numpy ndarray и еще один с typed memoryview, Тем не менее, я не мог получить memoryview версия работает быстрее, чем обычная версия (в отличие от многих блогов, таких как тесты памяти).

Будем весьма благодарны за любые советы / предложения по увеличению скорости просмотра кода памяти по сравнению с альтернативой numpy! ... ИЛИ... если кто-то может указать на какую-то явную причину, почему версия с обзором памяти не намного быстрее, чем обычная версия с numpy

В приведенном ниже коде есть две функции, каждая из которых принимает два вектора bi а также xi и возвращает матрицу. Первая функция shrink_correl это обычная версия NumPy и вторая функция shrink_correl2 альтернатива памяти (пусть файл будет sh_cor.pyx).

# cython: boundscheck=False
# cython: wraparound=False
# cython: cdivision=True

cimport cython
cimport numpy as np
import numpy as np
from numpy cimport ndarray as ar

# -- ***this is the Regular Cython version*** -
cpdef ar[double, ndim=2, mode='c'] shrink_correl(ar[double, ndim=1, mode='c'] bi, ar[double, ndim=1, mode='c'] xi):
    cdef:
        int n_ = xi.shape[0]
        int n__ = int(n_*(n_-1)/2)
        ar[double, ndim=2, mode='c'] f = np.zeros([n__, n_+1])
        int x__ = 0
        ar[double, ndim=2, mode='c'] f1 = np.zeros([n_, n_+1])
        ar[double, ndim=2, mode='c'] f2 = np.zeros([n__, n_+1])
        ar[double, ndim=1, mode='c'] g = np.zeros(n_+1)
        ar[double, ndim=1, mode='c'] s = np.zeros(n__)
        ar[double, ndim=2, mode='c'] cori_ = np.zeros([n_, n_])
        Py_ssize_t j, k

    with nogil:
        for j in range(0, n_-1):
            for k in range(j+1, n_):
                x__ += 1
                f[x__-1, j] = bi[k]*xi[k]*1000
                f[x__-1, k] = bi[j]*xi[j]*1000
    f1 = np.dot(np.transpose(f), f)      
    with nogil:
        for j in range(0, n_):
            f1[n_, j] = xi[j]*1000
            f1[j, n_] = f1[n_, j]
    f2 = np.dot(f, np.linalg.inv(f1))
    with nogil:
        for j in range(0, n_):
            g[j] = -bi[j]*xi[j]*1000

    s = np.dot(f2, g)

    with nogil:
        for j in range(0, n_):
            cori_[j, j] = 1.0
    x__ = 0

    with nogil:
        for j in range(0, n_-1):
            for k in range(j+1, n_):
                x__ += 1
                cori_[j, k] = s[x__-1]
                cori_[k, j] = cori_[j, k]
    return cori_

# -- ***this is the MemoryView Cython version*** -    
cpdef ar[double, ndim=2, mode='c'] shrink_correl2(double[:] bi, double[:] xi):
    cdef:
        int n_ = xi.shape[0]
        int n__ = int(n_*(n_-1)/2)
        double[:, ::1] f = np.zeros([n__, n_+1])
        int x__ = 0
        double[:, ::1] f1 = np.zeros([n_, n_+1])
        double[:, ::1] f2 = np.zeros([n__, n_+1])
        double[:] g = np.zeros(n_+1)
        double[:] s = np.zeros(n__)
        double[:, ::1] cori_ = np.zeros([n_, n_])
        ar[double, ndim=2, mode='c'] cori__ = np.zeros([n_, n_])
        Py_ssize_t j, k
    with nogil:
        for j in range(0, n_-1):
            for k in range(j+1, n_):
                x__ += 1
                f[x__-1, j] = bi[k]*xi[k]*1000
                f[x__-1, k] = bi[j]*xi[j]*1000
    f1 = np.dot(np.transpose(f), f)      
    with nogil:
        for j in range(0, n_):
            f1[n_, j] = xi[j]*1000
            f1[j, n_] = f1[n_, j]
    f2 = np.dot(f, np.linalg.inv(f1))
    with nogil:
        for j in range(0, n_):
            g[j] = -bi[j]*xi[j]*1000

    s = np.dot(f2, g)

    with nogil:
        for j in range(0, n_):
            cori_[j, j] = 1.0
    x__ = 0

    with nogil:
        for j in range(0, n_-1):
            for k in range(j+1, n_):
                x__ += 1
                cori_[j, k] = s[x__-1]
                cori_[k, j] = cori_[j, k]
    cori__[:, :] = cori_
    return cori__

Это скомпилировано с использованием следующего setup.py код

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy as np
import os

ext_modules = [Extension('sh_cor', ['sh_cor.pyx'], include_dirs=[np.get_include(),
                                                                 os.path.join(np.get_include(), 'numpy')],
                         define_macros=[('NPY_NO_DEPRECATED_API', None)],
                         extra_compile_args=['-O3', '-march=native', '-ffast-math', '-flto'],
                         libraries=['m']
                         )]

setup(
    name="Sh Cor",
    cmdclass={'build_ext': build_ext},
    ext_modules=ext_modules
)

Код, используемый для проверки скорости:

import numpy as np
import sh_cor  # this the library created by the setup.py file
import time

b = np.random.random(400)
b = b/np.sum(b)

x = np.random.random(400)-0.5

n = 10 

t0 = time.time()
for i in range(n):
    v1 = sh_cor.shrink_correl(b, x)
t1 = time.time()
print((t1-t0)/n)

t0 = time.time()
for i in range(n):
    v2 = sh_cor.shrink_correl2(b, x)
t1 = time.time()
print((t1-t0)/n)

Вывод на моем ПК:

0.7070999860763549   # regular numpy
0.6726999998092651   # memoryview

использование памяти (в приведенных выше кодах) дает мне увеличение скорости на 5% (в отличие от огромного увеличения скорости в блогах).

1 ответ

@uday Дайте мне около недели, так как я меньше работаю с компьютером, но вот где можно ускорить процесс, чтобы вы начали: 1) вместо того, чтобы подключать gil с помощью np.transpose создайте представление памяти, идентичное тому, что вы хотите транспонировать ДО любых циклов (т. е. у вас будет переменная f объявлен как вид памяти, который не понадобится GIL и просто создать представление о том, что f_tТо есть cdef double[:, ::1] f_T = np.transpose(f) или просто =f.T,

2) Этот шаг немного сложнее, так как вам нужна версия-оболочка в стиле C/C++ np.dot (поэтому в этом случае обязательно позвоните на dgemm функция with nogil: над ним & отступ для функции в следующей строке для освобождения gil с отступом в 4 пробела, который требуется SO): https://gist.github.com/pv/5437087. Этот пример выглядит хорошо (хотя вам нужно сохранить f2pyptr.h распакуйте и положите туда, где строится ваш проект; Я также подозреваю, что вы должны добавить cimport numpy as np); если нет, то для этого нужны моды, которые вы можете увидеть, как я сделал в другом посте здесь: прямой вызов BLAS / LAPACK с использованием интерфейса SciPy и Cython (проблема с указателем?)/- также как добавить MKL. Затем необходимо добавить from cython.parallel cimport prange вверху и измените все петли на prange от range и убедитесь, что все ваши prange разделы nogil и все переменные cdef заявлено до операции. Кроме того, вам придется добавить -openmp на ваш setup.py в аргументах компилятора, а также на ссылку на включаемые библиотеки. Задайте больше вопросов, если вам нужны разъяснения. Это не так просто, как должно быть, но с небольшим руководством становится довольно просто. В основном когда-то ваш setup.py изменено, чтобы включить все, что будет работать в будущем.

3) хотя это, вероятно, проще всего исправить - избавиться от этого списка. Если вам нужен текст и данные, сделайте их массивом данных или массивом панд. Всякий раз, когда я использовал списки для данных, замедление было невероятным.

Другие вопросы по тегам