Оптимизировать функции Cython, работающие со списками Python - PullRequest
0 голосов
/ 02 декабря 2018

В настоящее время я перевожу на Cython набор функций, которые в настоящее время реализованы в C ++ через scipy.weave (в настоящее время не рекомендуется).Эти функции работают с точками временных рядов, которые являются 2D-списками (например, [[17100, 19.2], [17101, 20.7], [17102, 20.3], ...]) как на входе, так и на выходе.Примером функции является subtract, которая принимает две временные серии и рассчитывает новую временную серию как вычитание двух входных данных, идущих от даты к дате.

Структура и интерфейсы должны поддерживаться для ретро-совместимости, но мое профилированиеИспытания показывают, что перенос Cython на 30-40% медленнее, чем в оригинальной реализации scipy.weave.Я перепробовал много способов оптимизации (внутренние преобразования в массивы Numpy и представления памяти, указатели C и т. Д.), Но необходимое время преобразования увеличивает общее время выполнения.Даже пытаясь определить вход и выход как векторы C ++, использование неявных преобразований Cython не кажется эффективным для поддержания скорости scipy.weave.Я также использовал различные подсказки для Boundscheck, Wraparound, Division, ...

Наибольшие замедления, кажется, для функций, которые используют вложенные циклы, и я видел, что небольшое усиление может предопределятьразмер списка (cdef list target = [[-1, float('nan')]]*size).

Я знаю, что Cython не может так много выполнять на структурах Python, особенно на списках, но есть ли другие приемы или приемы, с помощью которых можно получить ускорение?

=== РЕДАКТИРОВАТЬ - ДОБАВИТЬ ПРИМЕР КОДА === Хорошим примером типологии функций является следующее.Функция берет двумерный список дат / цен и двумерный список дат / десятичных коэффициентов и ищет совпадающие даты между двумя списками, вычисляя выходную информацию по соответствующей цене / коэффициенту путем умножения или деления (это третьвходной параметр).

Мой самый эффективный код Cython:

@cython.cdivision(True)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef apply_conversion(list original_timeserie, list factor_timeserie, int divide_or_multiply=False):
    cdef:
        Py_ssize_t i, j = 0, size = len(original_timeserie), size2 = len(factor_timeserie)
        long original_date, factor_date
        double original_price, factor_price, conv_price
        list result = []

    for i in range(size):
        original_date = original_timeserie[i][0]

        for j in range(j, size2):
            factor_date = factor_timeserie[j][0]

            if original_date == factor_date:
                original_price = original_timeserie[i][1]
                factor_price = factor_timeserie[j][1]

                if divide_or_multiply:
                    if factor_price != 0:
                        conv_price = original_price / factor_price
                    else:
                        conv_price = float('inf')
                else:
                    conv_price = original_price * factor_price

                result.append([original_date, conv_price])
                break

    return result

Оригинальная функция scipy:

int len = original_timeserie.length();
int len2 = factor_timeserie.length();

PyObject* py_serieconv = PyList_New(len);
PyObject* original_item = NULL;
PyObject* factor_item = NULL;
PyObject* date = NULL;
PyObject* value = NULL;
long original_date = 0;
long factor_date = 0;
double original_price = 0;
double factor_price = 0;

int j = 0;

for(int i=0;i<len;i++) {
    original_item = PyList_GetItem(original_timeserie, i);
    date = PyList_GetItem(original_item, 0);
    original_date  = PyInt_AsLong(date);
    original_price = PyFloat_AsDouble( PyList_GetItem(original_item, 1) );

    factor_item = NULL;
    for(;j<len2;) {
        factor_item  = PyList_GetItem(factor_timeserie, j++);
        factor_date = PyInt_AsLong(PyList_GetItem(factor_item, 0));
        if (factor_date == original_date) {
            factor_price = PyFloat_AsDouble(PyList_GetItem(factor_item, 1));
            value = PyFloat_FromDouble(original_price * (divide_or_multiply==0 ? factor_price : 1/factor_price));
            PyObject* py_new_item = PyList_New(2);
            Py_XINCREF(date);
            PyList_SetItem(py_new_item, 0, date);
            PyList_SetItem(py_new_item, 1, value);
            PyList_SetItem(py_serieconv, i, py_new_item);                        
            break;
        }
    } 
}

return_val = py_serieconv;
Py_XDECREF(py_serieconv);
...