#python #cython
#python #cython
Вопрос:
Я пытаюсь уменьшить особенно вычислительную нагрузку на код Python путем компиляции как C с помощью Cython. Удивительно, но я практически не добился успеха в его ускорении. С исходным модулем Python код выполняется примерно за 45 секунд, а с скомпилированным модулем Cython он выполняется примерно за 45 секунд.
При компиляции annotate=True
я остаюсь с морем желтых (иш) строк, предполагающих, что все еще существует огромное количество взаимодействий с Python.
Я ответил на это, отключив оба boundscheck (False)
и cdivision (True)
. Это не имело никакого эффекта. Следующий фрагмент кода является выдержкой из этого модуля и настаивает на взаимодействии с Python. Почему это так? Здесь ничего не нужно взаимодействовать с каким-либо уже существующим модулем Python, все очень просто арифметически?
cpdef float __distance_between(point1, point2) except? -2:
return ((point1[0] - point2[0]) ** 2 (point1[1] - point2[1]) ** 2) ** (1/2)
point1
и point2
являются ли списки Python, каждый из которых содержит 2 double
, например: [611811.997, -871083.372]
Результирующий C
код, который генерируется Cython для return
строки во фрагменте, выглядит следующим образом:
05: cpdef float __distance_between(point1, point2) except? -2:
06: return ((point1[0] - point2[0]) ** 2 (point1[1] - point2[1]) ** 2)**(1/2)
__pyx_t_1 = __Pyx_GetItemInt(__pyx_v_point1, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_2 = __Pyx_GetItemInt(__pyx_v_point2, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__pyx_t_3 = PyNumber_Subtract(__pyx_t_1, __pyx_t_2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_t_2 = PyNumber_Power(__pyx_t_3, __pyx_int_2, Py_None); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = __Pyx_GetItemInt(__pyx_v_point1, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3);
__pyx_t_1 = __Pyx_GetItemInt(__pyx_v_point2, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_4 = PyNumber_Subtract(__pyx_t_3, __pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__pyx_t_1 = PyNumber_Power(__pyx_t_4, __pyx_int_2, Py_None); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
__pyx_t_4 = PyNumber_Add(__pyx_t_2, __pyx_t_1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__pyx_t_1 = __Pyx_PyInt_From_long((1 / 2)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_2 = PyNumber_Power(__pyx_t_4, __pyx_t_1, Py_None); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__pyx_t_5 = __pyx_PyFloat_AsFloat(__pyx_t_2); if (unlikely((__pyx_t_5 == (float)-1) amp;amp; PyErr_Occurred())) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__pyx_r = __pyx_t_5;
goto __pyx_L0;
Как я могу лучше оптимизировать код или компиляцию, чтобы сделать этот фрагмент независимым от Python?
В этом модуле есть много других строк, которые все отображаются довольно желтыми, но, начиная с этого вопроса, я могу лучше понять, как справиться с остальными.
Ответ №1:
Вы не cdef
редактировали ни одну из своих переменных; вы знаете, что они float
есть, Cython этого не делает. Расскажите об этом.
cpdef float __distance_between((double, double)point1, (double, double)point2) except? -2:
return ((point1[0] - point2[0]) ** 2 (point1[1] - point2[1]) ** 2) ** (1/2)
Комментарии:
1. Примечание: убедитесь, что вы компилируете его с
-3
помощью синтаксиса to opt в Python 3 или1/2
будет интерпретироваться как0
; в качестве альтернативы, просто измените его на литерал0.5
(1. / 2.
или тому подобное), и он будет работать независимо от того, какой синтаксис (Py2 или Py3) вы используете.2. Да, это была проблема. И спасибо за указатель на
1/2
.3. Однако, если точки являются подлинными списками с плавающей запятой в Python, не существует типа данных Cython, который бы эффективно выражал это. Вы только что перенесли работу на некоторые преобразования в вызове функции.
4. @DavidW: это все равно значительно улучшит производительность. Конечно, вам нужно заранее выполнить преобразования, но это превращает кучу
PyNumber
общих вызовов API в необработанные операции C. Четыре распаковки и одна переупаковка — это не очень хорошо, но между ними все это необработанныеdouble
операции C, что намного лучше, чем шесть универсальныхPyNumber
операций (каждая из которых должна распаковать два операнда, выполнить математику и перепаковать результат в Pythonfloat
).5. @ShadowRanger да, справедливое замечание — я думаю, вы правы в том, что это поможет, и я думаю, что это все, что можно сделать с оптимизацией, не зная немного более точно, как OP использует функцию.