Нет заметного увеличения скорости с Cython: Как справиться с трансляцией NumPy на Cython? - PullRequest
0 голосов
/ 05 июля 2019

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

Моя оригинальная версия этой функции интенсивно использует трансляцию NumPy для больших многомерных массивов.При попытке Cythonize функции я сначала попытался преобразовать эти массивы в представления памяти Cython.Однако затем я столкнулся с ошибками компиляции, поскольку функция выполняет арифметику с этими массивами, и с тех пор я узнал, что это не поддерживается представлениями памяти.

Таким образом, я изменил свой Cythonized код, чтобы объявить массивыкак NumPy ndarrays, а не воспоминания.Функция Cythonized теперь работает, но не дает какой-либо заметной разницы в скорости по сравнению с исходной чистой версией Python / NumPy.

Поэтому я мог бы теперь либо: a.) Принять, что этот код не поддаетсяCythonized и искать в другом месте способ увеличения скорости, или б.) Вернитесь к обработке больших массивов как к просмотрам памяти и каким-то образом преодолеть арифметику, которая должна быть выполнена.

Для продолжения с опцией b.)Я хочу обратиться к арифметике в цикле for этой функции.Я мог бы потенциально написать дополнительные функции Cython, которые выполняют умножение и сложение массива поэлементно, что, как я понимаю, позволило бы мне использовать представления памяти.Однако, учитывая сложную трансляцию NumPy этого кода, я ожидаю, что это может потребовать значительных усилий (и я не обязательно знаю, как начать ...).Кроме того, я не уверен, принесут ли эти усилия свои плоды, учитывая, что выполнение действий по элементам (по общему признанию в C) на самом деле не может быть быстрее, чем операции широковещательной NumPy.

Я быОчень приветствую любые советы или поддержку.Спасибо.

from numpy import pi, exp, sin, cos
import numpy as np
cimport numpy as np
cimport cython

cdef np.ndarray[np.double_t, ndim=3] foo(bar, double dt, f, xi):
    cdef int nt, nh, nj
    cdef Py_ssize_t t

    nt = bar.shape[0]
    nh = bar.shape[1]

    if len(bar.shape) < 3:
        bar = bar[:, np.newaxis, :]
    cdef np.ndarray[np.double_t, ndim=3] bar_c = bar

    nj = len(f)
    k = (2 * pi * f) ** 2
    wn = k ** 0.5
    wd = (wn * (1 - xi ** 2) ** 0.5)

    cdef np.ndarray[np.double_t, ndim=3] u = np.zeros((nt, nj, nh))
    cdef np.ndarray[np.double_t, ndim=3] v = np.zeros((nt, nj, nh))

    C1 = exp(-xi * wn * dt)
    C2 = sin(wd * dt)
    C3 = cos(wd * dt)
    cdef np.ndarray[np.double_t, ndim=2] A11 = C1 * (C3 + (xi / (1 - xi ** 2) ** 0.5) * C2)
    cdef np.ndarray[np.double_t, ndim=2] A12 = (C1 / wd) * C2
    cdef np.ndarray[np.double_t, ndim=2] A21 = (-wn / (1 - xi ** 2) ** 0.5) * C1 * C2
    cdef np.ndarray[np.double_t, ndim=2] A22 = C1 * (C3 - (xi / (1 - xi ** 2) ** 0.5) * C2)
    cdef np.ndarray[np.double_t, ndim=2] B11 = C1 * (
        (((2 * xi ** 2 - 1) / (dt * wn ** 2)) + xi / wn) * C2 / wd
        + ((2 * xi / (dt * wn ** 3)) + (1 / wn ** 2)) * C3
    ) - 2 * xi / (dt * wn ** 3)
    cdef np.ndarray[np.double_t, ndim=2] B12 = (
        -C1
        * (
            ((2 * xi ** 2 - 1) / (dt * wn ** 2)) * C2 / wd
            + ((2 * xi) / (dt * wn ** 3)) * C3
        )
        - (1 / wn ** 2)
        + 2 * xi / (dt * wn ** 3)
    )
    cdef np.ndarray[np.double_t, ndim=2] B21 = -A12 - ((A11 - 1) / (dt * wn ** 2))
    cdef np.ndarray[np.double_t, ndim=2] B22 = -B21 - A12
    for t in range(0, nt - 1):
        u[t + 1, :, :] = (
            A11 * u[t, :, :]
            + A12 * v[t, :, :]
            + B11 * bar_c[t, :, :]
            + B12 * bar_c[t + 1, :, :]
        )
        v[t + 1, :, :] = (
            A21 * u[t, :, :]
            + A22 * v[t, :, :]
            + B21 * bar_c[t, :, :]
            + B22 * bar_c[t + 1, :, :]
        )

    cdef np.ndarray[np.double_t, ndim=3] out = -2 * xi * wn * v - (wn ** 2) * u - bar_c
    return out

Извинения за то, что не уменьшили размер кода.Учитывая характер запроса, мне трудно найти минимальный воспроизводимый пример.

1 Ответ

1 голос
/ 09 июля 2019

Спасибо @CodeSurgeon и @ 9000 за ваши ответы.Вы подтвердили / предположили, что в некоторых случаях код C действительно может дать увеличение скорости по сравнению с операциями NumPy - даже с учетом того факта, что представления памяти Cython требуют, чтобы операции C выполнялись поэлементно, а мой исходный код былиспользование операций NumPy, транслируемых по большим массивам.

Это побудило меня продолжить изучение.Работая с представлениями памяти, поэлементно, код теперь работает в 3–40 раз быстрее (сильно зависит от размера входных массивов, который будет варьироваться).

Модификации в основном выглядят такэто:

    # Memoryview declarations
    cdef double[:] u_mv = u
    cdef double[:] v_mv = v
    cdef double[:, :, :] out_mv = out
    cdef double[:, :, :] bar_mv = bar
    for j in range(nj):

        ...same calculation of constants C1, C2 etc. as before...

        for h in range(nh):
            for t in range(0, nt - 1):
                u_mv[t + 1] = (
                    A11 * u_mv[t]
                    + A12 * v_mv[t]
                    + B11 * bar_mv[t, 0, h]
                    + B12 * bar_mv[t + 1, 0, h]
                )
                v_mv[t + 1] = (
                    A21 * u_mv[t]
                    + A22 * v_mv[t]
                    + B21 * bar_mv[t, 0, h]
                    + B22 * bar_mv[t + 1, 0, h]
                )
                out_mv[t + 1, j, h] = (
                    -2 * xi_j * wn * v_mv[t + 1] - (wn ** 2) * u_mv[t + 1]
                    - bar_mv[t + 1, 0, h]
                )
            out_mv[0, j, h] = -bar_mv[0, 0, h]
    return out

Еще раз спасибо.

...