Gresces

Scalene: Python分析器

Guo Yangfan

本文介绍一下SCALENE的实现原理。SCALENE是一个专门为Python设计的分析器,能够同时分析CPU时间、内存使用情况和GPU使用情况,并提供细粒度(行粒度)的性能信息。

总之,按照文章的说明,它比之前的分析器都NB。

文章:OSDI 2023: Triangulating Python Performance Issues with Scalene

CPU 分析

传统方法

周期性地中断程序执行并检查当前的程序计数器(PC)。时间可以按照现实时间(CPU时间加IO等时间)计算,也可按照CPU时间(虚拟时间)计算。

原理:大样本情况下,PC所在进程的次数与进程执行时间成正比。

局限1:传统方法不能有效处理Python-本机代码交错执行的性能分析。在解释器没有获得控制权时,无法处理中断,所以执行本机代码时无法通过定时器中断进行抽样分析。

局限2:使用抽样方法难以分析子线程的执行时间。Python的信号由主线程处理,所以抽样仅对主线程计时,无法获得子线程的运行时间。

Figure1
Figure1 Figure1 抽样分析器依赖于定时器中断,但由于Python在执行本机代码时会将中断信号延迟到解释器获得控制权时处理,所以执行本机代码的时无法进行抽样分析。在图中,T表示执行本机代码的时间,这一段时间将无法被计入执行时间内。

本文贡献

技术1:精确的Python-本机代码交错执行计时

通过信号传递判断当前正在运行Python代码还是本机代码。如果中断信号被延迟处理,则正在运行本机代码,如Figure1所示。在两次中断之间,通过虚拟时间time.process_time()之差得到本机代码的执行时间。在中断到来时,通过遍历Python堆栈来寻找当前正在执行的代码行,从而对运行时间归因,提供细粒度的分析结果。

技术2:精确的子线程计时

  1. 重新定义主线程的阻塞行为,通过使用猴子补丁的方式将threading.join等阻塞函数修改为循环等待函数。从而保证主线程能够一直处理定时器中断。
  2. 为每个线程维护一个状态标志,主线程处理信号时,遍历所有线程,找到当前正在执行的线程,并获取其调用栈。
  3. 通过字节码反汇编,通过CALL(CALL, CALL_FUNCTION, CALL_METHOD)来分析当前执行的代码行。如果执行Python代码,则很快就会进入跳转目标,否则若执行本机代码,则会一直停留在CALL指令上。

内存分析

传统方法

Rate-Based Sampling:基于抽样,抽样的次数正比于分配或释放的内存数量,例如每分配或释放512KB采样一次。

局限:无法帮助定位内存泄漏。尽管可以记录内存分配的情况,以及被哪些代码占用了,可能存在内存问题,但不能说明这是内存泄漏。

本文方法

Figure2
Figure2 Figure2 两种采样方式的对比,菱形表示本文的采样方式,可以明显地看到采样次数的下降

原理:使用基于阈值的(Threshold-Based)采样而不是基于分配数量的采样。当内存的改变量大于设定阈值(|Alloca-Free|>Threshold)时,进行采样操作。

优势:降低了采样次数,并减少了运行时开销。如Figure2所示。

方法

  1. 拦截了系统内存分配器调用和Python的内置内存分配器,并替换为自己的shim分配器使用,已有代码改写系统分配器调用,并通过LD_PRELOAD(Linux)或者DYLD_INSERT_LIBRARIES(Mac OS X)注入。对于Python内部的分配器,使用Python的自定义API修改实现
  2. 在基于阈值的采样触发时,追踪产生申请或释放内存的语句,并通过后台线程记录统计信息。
  3. 在采样触发时,统计各个对象分配和释放的记录,并通过Laplace’s Rule of Succession来计算内存泄漏发生的可能性。

GPU 分析

方法 在配备NVIDIA GPU的系统上执行行级GPU利用率和内存分析,每进行一次CPU采样时,就收集当前使用的总GPU内存和利用率,并将其与当前执行的代码行相关联,从而帮助Python程序员确定他们是否有效地利用了GPU。

总结

亮点

本文的亮点是在CPU和内存上的抽样工作。

  1. 主要是CPU上的抽样,对于原生代码/外部库的运行时间区分以及多线程之间的运行时间区分,通过针对Python解释器的语言特性进行特殊化的处理,有可能可以拓展到其他的脚本/解释型语言上。
  2. 而对于内存的抽样,则体现在新的抽样策略的改进,并对于之前的分析器难以进行的内存泄漏检测提出了解决方案。

不足

  1. 对于GPU的分析像是狗尾续貂,强行增加来说明自己工作的NB。
  2. CPU的抽样只是基于某行代码的运行时间,未必能够表示CPU的实际占用率,例如sleep的执行。
  3. 内存分析中的泄漏检测是基于概率,还需要进一步地核实是否发生了内存泄漏,即有误报,同时无法证明没有漏报。

  1. OSDI 2023: Triangulating Python Performance Issues with Scalene
  2. OSDI 2023论文评述 Day1