Почему в Cython этот код работает медленнее, чем в Python?

Я начинаю изучать Cython из-за проблем с производительностью. Этот конкретный код является попыткой реализовать некоторые новые алгоритмы в области транспортного моделирования (для планирования).

Я решил начать с очень простой функции, которую я буду использовать МНОГО (сотни миллионов раз) и определенно выиграю от увеличения производительности.

Я реализовал эту функцию тремя разными способами и протестировал их для одного и того же параметра (для простоты) по 10 миллионов раз каждый:

  • Код Cython в модуле Cython. Время работы: 3,35 с

  • Код Python в модуле Cython. Время работы: 4,88 с

  • Код Python в основном скрипте. Продолжительность: 2,98 с

    Как видите, код cython был на 45% медленнее, чем код python в модуле cython, и на 64% медленнее, чем код, написанный в основном скрипте. Как такое возможно? Где я ошибаюсь?

Код cython таков:

def BPR2(vol, cap, al, be):
    con=al*pow(vol/cap,be)
    return con


def func (float volume, float capacity,float alfa,float beta):
    cdef float congest
    congest=alfa*pow(volume/capacity,beta)
    return congest

А сценарий для тестирования такой:

agora=clock()
for i in range(10000000):
    q=linkdelay.BPR2(10,5,0.15,4)

agora=clock()-agora
print agora

agora=clock()
for i in range(10000000):
    q=linkdelay.func(10,5,0.15,4)
    
agora=clock()-agora
print agora

agora=clock()
for i in range(10000000):
    q=0.15*pow(10/5,4)
    
agora=clock()-agora
print agora

Я знаю, что трансцендентные функции (мощность) работают медленнее, но я не думаю, что это должно быть проблемой.

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

Для справки я использую:

  • Windows 7 64-битная
  • Python 2.7.3 64 бит
  • Cython 0.16 64 бит
  • Windows Visual Studio 2008

person PCamargo    schedule 25.06.2012    source источник
comment
Итак, если вы думаете о передаче массива в функцию, по-видимому, вы можете векторизовать код, и в этом случае вы подумывали о том, чтобы делать то, что вы пытаетесь сделать, просто с помощью NumPy? Конечно, функцию в вашем примере можно тривиально реализовать на массивах с помощью NumPy.   -  person Henry Gomersall    schedule 25.06.2012
comment
Что ж, это чрезвычайно тривиальная функция, и cython действительно должен преобразовать PyObject* в число с плавающей запятой, а затем обратно, не так ли? Похоже, для такой маленькой функции много накладных расходов.   -  person Voo    schedule 25.06.2012
comment
Чтобы прояснить, ваша проблема в том, что вы тратите большую часть своего времени на вызов функции, которая не может быть улучшена с помощью Cython. Я предлагаю вам перефразировать свой вопрос без ущерба для решения (Cython). Таким образом, тем, кто привык отвечать, будет больше работы. Был бы полезен небольшой пример того, как вы на самом деле используете код.   -  person Henry Gomersall    schedule 25.06.2012
comment
Разве вам не нужно использовать данные типа double вместо float?   -  person K. Brafford    schedule 03.07.2012
comment
Как видите, код cython был на 45% медленнее, чем код python в модуле cython нет, вы сказали, что cython занял 3,35 секунды, а python в cython - 4,88 секунды. В моем мире это на 45% быстрее.   -  person Michel Jung    schedule 05.09.2018


Ответы (3)


Тестирование проводилось с использованием:

for i in range(10000000):
  func(2.7,2.3,2.4,i)

Вот результаты:

cdef float func(float v, float c, float a, float b):
  return a * (v/c) ** b
#=> 0.85

cpdef float func(float v, float c, float a, float b):
  return a * (v/c) ** b
#=> 0.84

def func(v,c,a,b):
  return a * pow(v/c,b)
#=> 3.41

cdef float func(float v, float c, float a, float b):
  return a * pow(v/c, b)
#=> 2.35

Для максимальной эффективности вам необходимо определить функцию на C и сделать тип возвращаемого значения статическим.

person Kassym Dorsel    schedule 25.06.2012
comment
Из любопытства, поправятся ли последние 2, если вы from libc.math cimport pow? - person mgilson; 25.06.2012
comment
У меня теперь 3,35 и 1,35 соответственно. Так да. - person Kassym Dorsel; 25.06.2012
comment
Несомненно, любое сокращение времени на установку статического возвращаемого типа во внешней библиотеке C lib будет незначительно из-за накладных расходов на вызовы. Все ваши увеличения скорости сводятся к минимизации вызовов библиотек python. Кроме того, мне любопытно, что делает cython, чтобы оператор ** работал быстрее, чем pow libc.math. Есть ли шанс опубликовать выведенный код C? - person Henry Gomersall; 26.06.2012
comment
Я только что проверил. Cython использует powf, что, я думаю, быстрее на вашей машине. Кстати, Cython уже устанавливает статический тип возвращаемого значения. - person Henry Gomersall; 26.06.2012
comment
Я заменил функцию pow на **, и все стало быстрее примерно на 30%. Я также протестировал цикл внутри функции (просто перебирая те же параметры, пересчитывая те же 10 миллионов раз), и Cython на самом деле в 3 раза быстрее, чем python, скомпилированный как cython, и в 8 раз быстрее, чем чистый python в основном скрипте ... - person PCamargo; 26.06.2012
comment
Я предполагаю, что проблема заключалась в накладных расходах при слишком частом вызове функции ... - person PCamargo; 26.06.2012
comment
@ user1480643, даже если вы используете cython, у вас все равно будут большие накладные расходы на вызовы в домене python, если вы не измените способ работы. Вам необходимо работать с массивами, которые могут быть статически типизированы и сокращены до простого C, чтобы максимально использовать возможности Cython. Серьезно, попробуйте ответить на свой вопрос с более широкой точки зрения. - person Henry Gomersall; 26.06.2012
comment
@Henry Gomersall, Что вы имеете в виду, говоря о массивах, которые можно статически типизировать? Я буду работать над Python большую часть времени (особенно, чтобы воспользоваться преимуществами реализаций разреженных матриц NumPy), поэтому мои массивы будут массивами python ... - person PCamargo; 26.06.2012
comment
На практике я имею в виду массивы numpy. Дело в том, что с массивом numpy вы можете написать цикл (в основном) на python, а затем увидеть на C, что он примерно такой же жесткий, как если бы он был закодирован вручную. - person Henry Gomersall; 26.06.2012

Эту функцию можно оптимизировать как таковую (как в python, так и в cython удаление промежуточной переменной происходит быстрее):

def func(float volume, float capacity, float alfa,f loat beta):
    return alfa * pow(volume / capacity, beta)
person C0deH4cker    schedule 25.06.2012
comment
Это точно не приведет к желаемому увеличению скорости ... - person Henry Gomersall; 25.06.2012
comment
Но это поможет. Попробуйте это, а затем посмотрите, на что это влияет. - person C0deH4cker; 25.06.2012
comment
Нет, не будет. В этом суть проблемы с преждевременной оптимизацией. Приложите свои усилия к усовершенствованию алгоритмов. - person Henry Gomersall; 25.06.2012

Когда Cython работает медленнее, это, вероятно, связано с преобразованием типов и, возможно, усугубляется отсутствием аннотаций типов. Кроме того, если вы используете структуры данных C в Cython, это будет быстрее, чем использование структур данных Python в Cython.

Я провел сравнение производительности между CPython 2.x (с Cython и без, с Psyco и без него), CPython 3.x (с Cython и без), Pypy и Jython. Pypy был безусловно самым быстрым, по крайней мере, для исследованного микро-теста: http://stromberg.dnsalias.org/~strombrg/backshift/documentation/performance/

person user1277476    schedule 25.06.2012