Нетривиальное перекодирование: как ускорить мою программу? Cython, Numba, многопроцессорность и NumPy?

У меня есть (или на самом деле работает) программа (стратегия торговли некоторыми парами), которая выполняет следующие действия:

  1. Получите подмножество больших данных (финансовые данные: индекс даты и времени и цены на акции для ~100 акций), установленных в базе данных postgres.
  2. Очистите данные (отбросьте запасы с>30% NaN) и рассчитайте доходность и индексацию (относительно первого наблюдения каждого запаса)
  3. Найти все комбинации пар акций, рассчитать корреляцию (на самом деле какая-то мера похожа на эту, но здесь слишком важна)
  4. Ранжирование пар с наивысшей корреляцией по наименьшим или только парам выбора с корреляцией> определенного порога, т.е.
  5. Проверьте каждую из этих пар на коинтеграцию, оба пути! и ранжировать их в соответствии с их тестовым значением
  6. Выберите лучшие n, то есть 10 пар, для торговли и рассчитайте некоторый сигнал на основе скользящих средних и стандартного
  7. Получить окно "вне образца" и обменять акции
  8. Запись ежедневного возвращения (т.е. в течение 5 дней) в бортовой журнал
  9. Подсчитайте немного статистики

После этих 9 шагов, начните снова, найдите другое окно обучения и выполните анализ...

Мой подход был бы - и, пожалуйста, исправьте, если вы видите что-то лучше:
1. Извлечь как можно больше функций из программы
2. Выполните шаги 1-9 через несколько окон обучения и торговли

и мой результирующий вопрос (вдохновленный многими темами здесь на форуме и т. е. как заставить ваш код Python работать быстрее

  • Как я могу определить, какая часть моего кода может выполняться параллельно?
  • Каким-то образом мне это кажется тривиальным: какую технику применить, чтобы "переписать" код, чтобы он мог использовать многопроцессорность?
  • Также не всегда очевидно: переписывать циклы как функции, какой-либо определенный угол, на который всегда нужно смотреть?
  • Имеет ли смысл "numba.jit()"все функции?
  • Должен ли я изменить все форматы моих данных на float64? какой недостаток может случиться? (на данный момент они "стандартные" числовые)
  • Есть ли контрольный список, из которого я вижу, когда цикл может быть векторизован?

Пожалуйста, извинитесь за многие - довольно концептуальные - вопросы, но я думаю, что если бы я мог понять все вышеперечисленные "болевые" моменты, это действительно улучшило бы мое "логическое" понимание, а также было бы очень полезно для новых присоединяющихся к Python.

1 ответ

Решение

Нетривиальные вопросы могут привести к упрощенным ответам:

Улучшения производительности требуют большого внимания, ~ [man*decades]...

Проще говоря, не ждите, чтобы прочитать несколько примеров и стать экспертом в этом.

№ 1: плохой алгоритм никогда не улучшится только с помощью некоторого (полу) автоматического преобразования. Интеллектуальный рефакторинг может увеличить производительность на +100% в собственном простом коде Python (пример ниже), но хорошо продуманный код, соответствующий свойствам устройства исполнения кода, близким к кремниевому, покажет такие усилия в другом свете, в результате улучшения кода в упомянутом буме производительности на ~ +100% производительность почти одинакова после преобразования в jit скомпилированный блок. Это означает, что преждевременные оптимизации могут оказаться бесполезными при переходе к тщательно спроектированному высокопроизводительному коду. По крайней мере, вы были предупреждены.

python отличный инструмент, я люблю его для почти бесконечно точной математики. Тем не менее, достижение максимальной точности и максимальной производительности кажется ближе к принципу Гейзенберга, чем специалисты по вычислительной технике, и тем больше поклонники стремятся это признать. Просто потратил много времени на то и другое, чтобы уложить его в пара слов.


Q4: имеет ли смысл " numba.jit() "все функции?

numba отличный инструмент для стабильной базы кода, поэтому начнем с него:

Низко висящие плоды автоматических трансформаций легко собирать numba.jit() инструменты. Бенчмаркинг поможет вам сэкономить почти все накладные расходы, которые ваш код не должен нести.

Если в зависимости от элементов кода, то все еще развивается numba.jit() преобразователи кода не могут перекодировать, все готово. Работая с numba так как это очень первые релизы, { list | dict | class | ... } являются убийцами для любых дальнейших мечтаний о том, чтобы код (авто) преобразовался немного ближе к кремнию. Также все упомянутые функции должны быть в состоянии получить numba.jit() так что почти забудь о высоком уровне import кодовые базы легко переводятся с помощью numba, если их оригинальный код не был систематически разработан с numba в уме.


В5: Должен ли я изменить все форматы моих данных на float64?
какой недостаток может случиться? (на данный момент они "стандартные" числовые)

float32 кроме вдвое уменьшенного статического размера памяти [SPACE] -домен, несколько принципиальных недостатков.

Некоторые модули (как правило, те, которые наследуются от числовых решателей FORTRAN и аналогичного наследия) автоматически преобразуют любые переданные извне данные в свои локальные float64 реплики (так что оба [SPACE] а также [TIME] штраф растет за пределами вашей области контроля).

Лучше ожидать увеличения штрафа за выполнение кода в [TIME] -домен, поскольку невыровненные границы ячеек являются дорогостоящими (это углубляется в уровень сборки кода, наборы инструкций ЦП и иерархии кэша для освоения всех деталей на этом уровне).

Может увидеть почти в 3 раза медленнее выполнение на float32 в тестах ниже.


Q6: есть ли контрольный список, из которого я могу видеть, когда цикл может быть векторизован?

Авто-векторизационные трансформаторы - не что иное как цель Нобелевской премии.

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

Профессиональные пакеты преобразования кода стоят дорого (кто-то должен платить за опыт, накопленный в течение многих [человек * лет]) и, как правило, могут корректировать свои ROI, только если они развернуты в масштабе.


Q1: Как я могу определить, какая часть моего кода может выполняться параллельно?

Вы счастливы, что вам не нужно разрабатывать свой код для запуска в [PARALLEL], но "просто"- [CONCURRENT] мода. Если кто-то говорит параллельное, проверьте, действительно ли система требует выполнения всех условий для истинного [PARALLEL] процесс планирования, в большинстве случаев "просто"- [CONCURRENT] Планирование - это то, что просил докладчик (подробности выходят за рамки этого поста). Блокировка Python GIL предотвращает любое, кроме подпроцессного, разделение рабочего процесса, но за счет затрат, поэтому соберите вашу независимость от обработки, поскольку это вознаградит ваши намерения, не платя никаких дополнительных затрат на дополнительные накладные расходы, если идет против правила накладных - строгий закон Амдала.


Бенчмарк, бенчмарк, бенчмарк:

Фактическое соотношение цены и эффекта необходимо будет проверить на соответствующей версии архитектуры python, numba, CPU / cache, поэтому сравнительный анализ - единственный способ подтвердить любое улучшение (по затратам).

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

def plain_EMA_fromPrice(                   N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice_float32(           N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float32_nopython(  N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice_float64(           N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float64_nopython(  N_period, aPriceVECTOR ):
    ...    


def plain_EMA_fromPrice2(                  N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice2_float32(          N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float32_nopython( N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice2_float64(          N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float64_nopython( N_period, aPriceVECTOR ):
    ...

Улучшение производительности
от 710 [us] -> 160 [us] с помощью ре-факторинга кода и выравнивания памяти
Downto -> 12 ~ 17 [us] от numba.jit():

>>> aPV_32 = np.arange( 100, dtype = np.float32 )
>>> aPV_64 = np.arange( 100, dtype = np.float32 )

>>> aClk.start();_ = plain_EMA_fromPrice(                  18, aPV_32 );aClk.stop()
715L
723L
712L
722L
975L

>>> aClk.start();_ = plain_EMA_fromPrice(                  18, aPV_64 );aClk.stop()
220L
219L
216L
193L
212L
217L
218L
215L
217L
217L

>>> aClk.start();_ = numba_EMA_fromPrice_float32(          18, aPV_32 );aClk.stop()
199L
15L
16L
16L
17L
13L
16L
12L

>>> aClk.start();_ = numba_EMA_fromPrice_float64(          18, aPV_64 );aClk.stop()
170L
16L
16L
16L
18L
14L
16L
14L
17L

>>> aClk.start();_ = numba_EMA_fromPrice_float64_nopython( 18, aPV_64 );aClk.stop()
16L
17L
17L
16L
12L
16L
14L
16L
15L

>>> aClk.start();_ = plain_EMA_fromPrice2(                 18, aPV_32 );aClk.stop()
648L
654L
662L
648L
647L

>>> aClk.start();_ = plain_EMA_fromPrice2(                 18, aPV_64 );aClk.stop()
165L
166L
162L
162L
162L
163L
162L
162L

>>> aClk.start();_ = numba_EMA_fromPrice2_float32(         18, aPV_32 );aClk.stop()
43L
45L
43L
41L
41L
42L

>>> aClk.start();_ = numba_EMA_fromPrice2_float64(         18, aPV_64 );aClk.stop()
17L
16L
15L
17L
17L
17L
12L

>>> aClk.start();_ = numba_EMA_fromPrice2_float64_nopython( 18, aPV_64 );aClk.stop()
16L
15L
15L
14L
17L
15L
Другие вопросы по тегам