Как набрать функцию генератора в Cython? - PullRequest
0 голосов
/ 16 мая 2018

Если у меня есть функция генератора в Python, скажите:

def gen(x):
    for i in range(x):
        yield(i ** 2)

Как мне объявить, что выходной тип данных в Cython int? Это даже стоит?

Спасибо.

Редактировать: Я прочитал упоминания (асинхронных) генераторов, которые были реализованы в журнале изменений: http://cython.readthedocs.io/en/latest/src/changes.html?highlight=generators#id23

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

1 Ответ

0 голосов
/ 16 мая 2018

Нет, в Cython нет способа сделать это.

Когда вы посмотрите на код, созданный на Cython, вы увидите, что gen (и другие функции генератора) возвращают генератор, которыйв основном это __pyx_CoroutineObject объект, который выглядит следующим образом :

typedef PyObject *(*__pyx_coroutine_body_t)(PyObject *, PyThreadState *, PyObject *);
typedef struct {
    PyObject_HEAD
    __pyx_coroutine_body_t body;
    PyObject *closure;
    ...
    int resume_label;
    char is_running;
} __pyx_CoroutineObject;

Самая важная часть - body-член: это функция, которая выполняет фактический расчет.Как мы видим, он возвращает PyObject, и нет способа (пока?) Для его адаптации к int, double или подобному.

Что касается причин, по которым это не сделаноЯ могу только строить догадки - но, вероятно, есть более чем одна причина.

Если вы действительно заботитесь о производительности, генераторы в любом случае вносят слишком много накладных расходов (например, yield невозможна в cdef -функциях) и должен быть переработан во что-то более простое.


Более подробно о возможных рефакторингах.Исходя из предположения, что мы хотели бы суммировать все созданные значения:

%%cython 
def gen(int x):
    cdef int i
    for i in range(x):
        yield(i ** 2)

def sum_it(int n):
    cdef int i
    cdef int res=0
    for i in gen(n):
        res+=i
    return res

Сроки это приводит к:

>>> %timeit sum_it(1000)
28.9 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Хорошая новость: это примерно в 10 раз быстрее, чем чистыеверсия Python, но если мы действительно после скорости:

%%cython 
cdef int gen_fast(int i):
    return i ** 2

def sum_it_fast(int n):
    cdef int i
    cdef int res=0
    for i in range(n):
        res+=gen_fast(i)
    return res

Это:

>>> %timeit sum_it_fast(1000)
661 ns ± 20.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

примерно в 50 раз быстрее.

Я понимаю, это довольноизменение, и это может быть довольно сложно сделать - я бы сделал это, только если это действительно является узким местом моей программы - но тогда ускорение 50 было бы реальной мотивацией для этого.

Очевидно, что естьЕсть много других подходов: использование numpy-массивов или array.array вместо генераторов или написание собственного генератора (cdef-class), который обеспечил бы дополнительную быструю / эффективную возможность получить int -значения, а не PyObjects- но это все зависит от вашего сценария под рукой.Я просто хотел показать, что есть потенциал для улучшения производительности, отключив генераторы.

...