Как уменьшить взаимодействие скомпилированного кода Cython с Python

#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 операций (каждая из которых должна распаковать два операнда, выполнить математику и перепаковать результат в Python float ).

5. @ShadowRanger да, справедливое замечание — я думаю, вы правы в том, что это поможет, и я думаю, что это все, что можно сделать с оптимизацией, не зная немного более точно, как OP использует функцию.