将 line_profiler 与 numba jitted 函数一起使用

sud*_*udo 9 python performance profiling numba line-profiler

是否可以将 line_profiler 与 Numba 一起使用?

调用%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
Run Code Online (Sandbox Code Playgroud)

Cython 代码有一个解决方法,但找不到 Numba 的任何内容。

MSe*_*ert 7

TL;DR:对 numba 函数进行线分析可能(技术上)不可能,但即使可以对 numba 函数进行线分析,结果也可能不准确。

分析器和编译/优化语言的问题

将探查器与“编译”语言一起使用很复杂(甚至在某种程度上扩展到非编译语言,具体取决于运行时允许做什么),因为编译器可以重写您的代码。仅举几个例子:常量折叠内联函数调用展开循环(利用SIMD 指令)、提升和通常重新排序/重新排列表达式(甚至在多行上)。一般来说,只要结果和副作用“好像”函数没有“优化” ,编译器就可以做任何事情。

示意图:

+---------------+       +-------------+      +----------+
|  Source file  |   ->  |  Optimizer  |  ->  |  Result  |
+---------------+       +-------------+      +----------+
Run Code Online (Sandbox Code Playgroud)

这是一个问题,因为探查器需要在代码中插入语句,例如,函数探查器可能会在每个函数的开头和开头插入一条语句,即使代码已优化并且函数已内联,这也可能有效 - 仅仅是因为“分析器语句”也是内联的。但是,如果编译器由于额外的分析器语句而决定内联函数怎么办?那么您所描述的内容实际上可能与“真实程序”的执行方式不同。

例如,如果你有(我在这里使用 Python,即使它没有被编译,假设我用 C 左右编写了这样一个程序):

 def give_me_ten():
     return 10

 def main():
     n = give_me_ten()
     ...
Run Code Online (Sandbox Code Playgroud)

然后优化器可以将其重写为:

 def main():
     n = 10  # <-- inline the function
Run Code Online (Sandbox Code Playgroud)

但是,如果您插入探查器语句:

 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')
Run Code Online (Sandbox Code Playgroud)

优化器可能只是发出相同的代码,因为它没有内联函数。

行分析器实际上在您的代码中插入了更多的“分析器语句”。在每行的开头和结尾。这可能会阻止很多编译器优化。我对“as-if”规则不太熟悉,但我的猜测是很多优化是不可能的。因此,您使用分析器编译的程序的行为将与没有分析器的编译程序显着不同。

例如,如果你有这个程序:

 def main():
     n = 1
     for _ in range(1000):
         n += 1
     ...
Run Code Online (Sandbox Code Playgroud)

优化器可以(不确定是否有任何编译器会这样做)将其重写为:

 def main():
     n = 1001  # all statements are compile-time constants and no side-effects visible
Run Code Online (Sandbox Code Playgroud)

但是,如果您有行分析语句,则:

 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)
     ...
Run Code Online (Sandbox Code Playgroud)

然后根据“as-if”规则,循环有副作用,不能压缩为单个语句(也许代码仍然可以优化,但不能作为单个语句)。

请注意,这些只是简单的例子,编译器/优化器通常非常复杂并且有很多可能的优化。

根据语言、编译器和分析器,可能可以减轻这些影响。但是面向 Python 的分析器(例如 line-profiler)不太可能针对 C/C++ 编译器。

还要注意,这不是 Python 的真正问题,因为 Python 只是真正一步一步地执行程序(不是真的,但 Python 很少更改您的“编写的代码”,而且只以较小的方式更改)。

这如何适用于 Numba 和 Cython?

  • Cython 将您的 Python 代码转换为 C(或 C++)代码,然后使用 C(或 C++)编译器来编译它。示意图:

     def give_me_ten():
         return 10
    
     def main():
         n = give_me_ten()
         ...
    
    Run Code Online (Sandbox Code Playgroud)
  • Numba 根据参数类型翻译 Python 代码,并使用 LLVM 编译代码。示意图:

     def main():
         n = 10  # <-- inline the function
    
    Run Code Online (Sandbox Code Playgroud)

两者都有一个可以进行广泛优化的编译器。如果在编译之前将分析语句插入到代码中,许多优化将无法实现。因此,即使可以对代码进行行分析,结果也可能不准确(在实际程序将以这种方式执行的意义上是准确的)。

Line-profiler 是为纯 Python 编写的,所以如果 Cython/Numba 工作正常,我不一定相信它的输出。它可能会给出一些提示,但总的来说它可能太不准确了。

特别是 Numba 可能真的很棘手,因为 numba 翻译器需要支持分析语句(否则你最终会得到一个对象模式的 numba 函数,它会产生完全不准确的结果)并且你的 jitted 函数不再只是一个函数了。它实际上是一个调度程序,根据参数的类型委托给“隐藏”函数。因此,当您使用 anint或 a调用相同的“调度程序”时,float它可以执行完全不同的功能。有趣的事实:使用函数分析器进行分析的行为已经产生了很大的开销,因为 numba 开发人员想要使其工作(请参阅cProfile 在调用 numba jit 函数时增加了大量开销)。

好的,如何描述它们?

您可能应该使用可以与编译器一起处理已翻译代码的分析器进行分析。这些可以(可能)产生比为 Python 代码编写的分析器更准确的结果。这将更加复杂,因为这些分析器将返回必须再次手动传输到原始代码的翻译代码的结果。此外,它甚至可能是不可能的——通常 Cython/Numba 管理结果的翻译、编译和执行,因此您需要检查它们是否为额外的分析器提供了钩子。我在那里没有经验。

作为一般规则:如果您有优化器,则始终将分析视为“指南”,而不一定是“事实”。并且始终使用专为编译器/优化器设计的分析器,否则您将失去很多可靠性和/或准确性。