是否可以在Numba中使用line_profiler?
在用%lprun
装饰的函数上调用@numba.jit
会返回一个空的配置文件:
Timer unit: 1e-06 s
Total time: 0 s
File: <ipython-input-29-486f0a3cdf73>
Function: conv at line 1
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 @numba.jit
2 def conv(f, w):
3 f_full = np.zeros(np.int(f.size + (2 * w.size) - 2), dtype=np.float64)
4 for i in range(0, f_full.size):
5 if i >= w.size - 1 and i < w.size + f.size - 1:
6 f_full[i] = f[i - w.size + 1]
7 w = w[::-1]
8 g = np.zeros(f_full.size-w.size + 1, dtype=np.float64)
9 for i in range(0, f_full.size - w.size):
10 g[i] = np.sum(np.multiply(f_full[i:i+w.size], w))
11 return g
Cython代码有一个解决方法,但Numba找不到任何东西。
答案 0 :(得分:1)
TL; DR :(技术上)可能无法对numba函数进行行概要分析,但是即使可以对numba函数进行行轮廓分析,结果 也可能不准确。
使用带有“已编译”语言的探查器很复杂(根据允许的运行时,甚至可以使用未编译的语言进行扩展),因为允许编译器重写您的代码。仅举几个例子:constant folding,inline function calls,unroll loops(以利用SIMD instructions的优势),hoisting,以及通常对表达式进行重新排序/重新排列(甚至多行)。通常,只要结果没有副作用且"as if"函数未“优化”,编译器就可以执行任何操作。
示意图:
+---------------+ +-------------+ +----------+
| Source file | -> | Optimizer | -> | Result |
+---------------+ +-------------+ +----------+
这是一个问题,因为探查器需要在代码中插入语句,例如,函数探查器可能会在每个函数的开头和开头插入一条语句,即使对代码进行了优化并且内联了函数,也可能会起作用-仅仅因为内嵌了“ profiler语句”。但是,如果编译器由于附加的事件探查器语句而决定不内联函数怎么办?那么,您配置的内容实际上可能与“真实程序”的执行方式有所不同。
例如,如果您拥有(即使未编译,我仍在这里使用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')
优化器可能只是发出相同的代码,因为它没有内联函数。
line-profiler实际上在您的代码中插入了更多的“ profiler语句”。在每行的开头和结尾。这可能会阻止很多编译器优化。我对“假设”规则不太熟悉,但是我猜想当时不可能进行很多优化。因此,具有事件探查器的已编译程序的行为将与不具有事件探查器的已编译程序的行为明显不同。
例如,如果您有此程序:
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非常非常很少地更改您的“编写的代码”,然后仅以较小的方式)。
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 adds significant overhead when calling numba jit functions)。
您可能应该使用可以与转换后的代码上的编译器一起使用的探查器进行探查。与为Python代码编写的探查器相比,它们可能(可能)产生更准确的结果。这将更加复杂,因为这些事件探查器将返回必须自动重新传输到原始代码的翻译代码的结果。也可能甚至无法实现-通常,Cython / Numba管理结果的翻译,编译和执行,因此您需要检查它们是否为其他探查器提供了挂钩。我在那里没有经验。
通常,如果您有优化器,则总是会将概要分析视为“指南”,而不必视为“事实”。并且始终使用专为编译器/优化器设计的探查器,否则您将失去很多可靠性和/或准确性。