TL; DR: профилирование строки функции numba может быть (технически) невозможно, но даже если было возможно профилирование строки функции numba, результаты могут быть неточными.
Проблема с профилировщиками и скомпилированными / оптимизированными языками
Сложно использовать профилировщики с «скомпилированными» языками (даже в некоторых случаях с некомпилированными языками в зависимости от того, что разрешено делать во время выполнения), потому что компиляторыразрешено переписать ваш кодВот лишь несколько примеров: постоянное свертывание , вызовы встроенных функций , развертывание циклов (чтобы воспользоваться инструкциями SIMD ), подъем и, как правило, переупорядочивание / перестановка выражений (даже в несколько строк).Как правило, компилятору разрешено делать все что угодно, пока результат и побочные эффекты равны "как если бы" функция не была "оптимизирована".
Схема:
+---------------+ +-------------+ +----------+
| Source file | -> | Optimizer | -> | Result |
+---------------+ +-------------+ +----------+
Это проблема, потому что профилировщик должен вставлять операторы в код, например, профилировщик функции может вставлять оператор в начале и в начале каждой функции, что может работать, даже если код оптимизирован и функциявстроен - просто потому, что встроены «операторы профилировщика».Однако что, если компилятор решит , а не , чтобы встроить функцию из-за дополнительных операторов профилировщика?Тогда то, что вы профилируете, может фактически отличаться от того, как будет работать «настоящая программа».
Например, если у вас есть (я использую Python здесь, даже если он не скомпилирован, просто предположим, что я написал такую программу на C илиитак):
def give_me_ten():
return 10
def main():
n = give_me_ten()
...
Тогда оптимизатор может переписать его следующим образом:
def main():
n = 10 # <-- inline the function
Однако, если вы вставите операторы профилировщика:
def give_me_ten():
profile_start('give_me_ten')
n = 10
profile_end('give_me_ten')
return n
def main():
profile_start('main')
n = give_me_ten()
...
profile_end('main')
Оптимизатор может просто выдатьтот же код, потому что он не встроен в функцию.
Строковый профилировщик фактически вставляет намного больше «операторов профилировщика» в ваш код.В начале и в конце каждой строки.Это может предотвратить много оптимизаций компилятора.Я не слишком знаком с правилом «как будто», но думаю, что тогда много оптимизаций невозможно.Таким образом, ваша скомпилированная программа с профилировщиком будет вести себя значительно иначе, чем скомпилированная программа без профилировщика.
Например, если у вас была эта программа:
def main():
n = 1
for _ in range(1000):
n += 1
...
Оптимизатор может (не уверен, что какой-либо компилятор сделает это) переписать его как:
def main():
n = 1001 # all statements are compile-time constants and no side-effects visible
Однакоесли у вас есть операторы профилирования строк, то:
def main():
profile_start('main', line=1)
n = 1
profile_end('main', line=1)
profile_start('main', line=2)
for _ in range(1000):
profile_end('main', line=2)
profile_start('main', line=3)
n += 1
profile_end('main', line=3)
profile_start('main', line=2)
...
Тогда по правилу «как если» цикл имеет побочные эффекты и не может быть сжат как один оператор (возможно, код все еще можно оптимизировать, ноне как одно утверждение).
Обратите внимание, что это упрощенные примеры, компиляторы / оптимизаторы, как правило, действительно сложные и имеют лотов возможных оптимизаций.
В зависимости от языкаКомпилятор и профилировщик могут уменьшить эти эффекты.Но маловероятно, что Python-ориентированный профилировщик (такой как line-profiler) предназначен для компиляторов C / C ++.
Также обратите внимание, что это не является реальной проблемой с Python, потому что Python просто выполняет программу действительно пошагово (не совсем верно, но Python очень, очень редко меняет ваш «написанный код», а затем лишь незначительными способами.
Как это относится к Numba и Cython?
Cython переводит ваш код Python в код C (или C ++), а затем использует компилятор C (или C ++) для его компиляции.Схема:
+-------------+ +--------+ +----------+ +-----------+ +--------+
| Source file | -> | Cython | -> | C source | -> | Optimizer | -> | Result |
+-------------+ +--------+ +----------+ +-----------+ +--------+
Numba переводит ваш код Python в зависимости от типов аргументов и использует LLVM для компиляции кода.Схема:
+-------------+ +-------+ +------------------+ +--------+
| Source file | -> | Numba | -> | LLVM / Optimizer | -> | Result |
+-------------+ +-------+ +------------------+ +--------+
Оба имеют компилятор, который может выполнять обширные оптимизации.Многие оптимизации не будут возможны, если вы вставите операторы профилирования в ваш код перед его компиляцией.Таким образом, даже если бы можно было выполнить линейный профиль кода, результаты могут быть неточными (точными в том смысле, что настоящая программа будет работать таким образом).
Line-profiler был написан для чистого Python, поэтому я не обязательно доверял бы выводу для Cython / Numba, если бы он работал.Это может дать некоторые подсказки, но в целом это может быть просто слишком неточно.
Особенно Numba может быть очень хитрым, потому что транслятор numba должен будет поддерживать операторы профилирования (в противном случае вы получите функцию numba в объектном режиме).что дало бы совершенно неточные результаты) и ваша функция с привязкой больше не просто одна функция.Это на самом деле диспетчер, который делегирует «скрытую» функцию в зависимости от типа аргументов.Поэтому, когда вы вызываете одного и того же «диспетчера» с int
или float
, он может выполнять совершенно другую функцию.Интересный факт: процесс профилирования с помощью профилировщика функций уже налагает значительные накладные расходы, потому что разработчики numba хотели выполнить эту работу (см. cProfile добавляет значительные накладные расходы при вызове функций numba jit ).
Хорошо, как их профилировать?
Вы, вероятно, должны профилировать с помощью профилировщика, который может работать с компилятором для переведенного кода .Они могут (вероятно) давать более точные результаты, чем профилировщик, написанный для кода Python.Это будет сложнее, потому что эти профилировщики будут возвращать результаты для переведенного кода , которые должны быть вручную переведены в исходный код снова.Также это может быть даже невозможно - обычно Cython / Numba управляет переводом, компиляцией и выполнением результата, поэтому вам нужно проверить, предоставляют ли они хуки для дополнительного профилировщика.У меня там нет опыта.
И, как правило, если у вас есть оптимизаторы, то всегда рассматривает профилирование как «руководство», а не как факт.И всегда используйте профилировщики, разработанные для компилятора / оптимизатора, иначе вы потеряете много надежности и / или точности.