Python: как профилировать код, написанный с помощью декораторов numba.njit () - PullRequest
4 голосов
/ 18 марта 2019

У меня довольно сложный вычислительный код, который я пытаюсь ускорить и многопоточность. Чтобы оптимизировать код, я пытаюсь определить, какие функции берут больше всего или вызывают больше всего.

У меня раньше не было профилированного кода, поэтому я мог что-то упустить. Однако я знаю, что многие существующие модули профилирования не очень хорошо работают с декоратором njit () numba из-за перекомпиляции с LLVM.

Таким образом, мой вопрос будет таким: Каков наилучший способ профилировать код, в котором большинство функций имеют декоратор njit () с несколькими непересекающимися функциями управления?

Раньше я сталкивался с data_profiler , однако, похоже, его больше нет в хранилище conda, и я не знаю, как его собрать из исходного кода в conda, или все равно быть совместимым с современными версиями его зависимостей.

1 Ответ

0 голосов
/ 02 июля 2019

Если это поможет в крайнем случае, давайте попробуем:

Потратив несколько десятков человек * лет на разработку модуля QuantFX, оба с использованием numba идругие инструменты векторизации / jit-ускорения, позвольте мне поделиться несколькими примерами опыта, которые были сочтены полезными для нашего аналогично мотивированного профилирования.

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

Все сервисы ZeroMQ реализованы в ядре ядра, называемом Context, но покаэто одна небольшая утилита, которую можно использовать повторно независимо от этого инструментария, Stopwatch - класс таймера с микросекундным разрешением.

Итак, ничто не может помешать нам:

from pyzmq import Stopwatch as MyClock

aClock_A = MyClock(); aClock_B = MyClock(); aClock_C = MyClock(); print( "ACK: A,B,C made" )

# may use 'em when "framing" a code-execution block:
aClock_A.start(); _ = sum( [ aNumOfCollatzConjectureSteps( N ) for N in range( 10**10 ) ] ); TASK_A_us = aClock_A.stop()
print( "INF: Collatz-task took {0:} [us] ".format( TASK_A_us ) )

# may add 'em into call-signatures and pass 'em and/or re-use 'em inside whatever our code
aReturnedVALUE = aNumbaPreCompiledCODE(  1234,
                                        "myCode with a need to profile on several levels",
                                        aClock_A, #     several, 
                                        aClock_B, # pre-instantiated,
                                        aClock_C  #     Stopwatch instances, so as
                                        )         #  to avoid chained latencies

Таким образом, если действительно использовать это в качестве инструмента последней инстанции, можно «жестко связать» собственный исходный код с любой структурой * 10.22 * профилирование.Единственным ограничением является необходимость соответствия конечного автомата экземпляра Stopwatch, когда после вызова метода .start() может последовать только метод .stop(), и аналогично, вызывая метод .stop()на еще не .start() -элементном экземпляре вполне естественно будет сгенерировано исключение.

Обычные try-except-finally леса помогут установить, что все экземпляры секундомера снова становятся .stop() -едами, даже если исключенияВозможно, это и произошло.

Структура «аппаратного» профилирования зависит от выполняемого вами кода «горячих точек при тестировании» и даже от «межграничного» профилирования накладных расходов, связанных с вызовом, проводимых между собственным Pythonвызов @ jit-оформленного кода numba-LLVM-ed и начало 1-й строки «внутри» скомпилированного кода numba (т. е. сколько времени занимает между вызовом-вызовом и анализом параметров, управляемым списком сигнатур вызововили, в принципе, этого можно избежать, применяя одну явную подпись вызова)

Удачи.Надеюсь, это поможет вам.

...