专家使用细粒度位置信息来创建可视化表示,精确显示CPython新的专门化、自适应解释器在何处以及如何优化您的代码。
入门
专家支持所有平台上的CPython 3.11+版本。
安装只需运行:
$ pip install specialist
如果您通常使用pytest
运行测试,那么可以尝试使用以下命令来代替:
$ specialist --output report --targets '**/*.py' -m pytest # 这里可以添加任何其他pytest选项
测试完成后,specialist
将创建一个名为report
的目录,并用当前目录树中每个模块的可浏览HTML可视化内容填充它。
背景
当CPython运行您的代码时,它会识别出运行频率足够高以值得花时间优化的"热点"区域。它偶尔会"加速"这些区域,specialist
用颜色来表示这一过程。深色、丰富的颜色表示具有许多加速指令的代码(因此具有高专门化潜力),而浅色、淡色表示相对较少专门化机会的代码。
大多数情况下,加速涉及三个阶段:
-
将单个字节码指令替换为"自适应"形式。这些实际上比普通指令稍慢,因为它们会定期尝试"专门化"自身。如果无法专门化,它们将保持自适应形式。
specialist
使用红色来表示自适应指令的存在。 -
偶尔,自适应指令会将自身转换为更快的"专门化"指令。专门化的例子包括对单个对象或类型的属性访问、某些纯Python调用或整数加法。
specialist
使用绿色来表示专门化指令的存在。 -
如果专门化在一段时间后变得无效(例如,如果之前加两个整数的表达式开始连接两个字符串),专门化指令可能会被转换回自适应指令。此时,循环重新开始。
专家旨在为CPython本身的维护者以及寻求自己代码优化配置的用户提供对这一过程的洞察。如果您想了解更多关于专门化的信息,请查看这个来自PyCon US 2023的演讲。
教程
假设我们有以下源文件conversions.py
,其中包含一些用于华氏度和摄氏度之间转换的实用程序和测试:
import math
def f_to_c(f: float) -> float:
"""将华氏度转换为摄氏度。"""
x = f - 32
return x * 5 / 9
def c_to_f(c: float) -> float:
"""将摄氏度转换为华氏度。"""
x = c * 9 / 5
return x + 32
TEST_VALUES = [-459.67, -273.15, 0.0, 32.0, 42.0, 273.15, 100.0, 212.0, 373.15]
def test_conversions() -> None:
for t in TEST_VALUES:
assert_round_trip(t)
def assert_round_trip(t: float) -> None:
# 将华氏度通过摄氏度进行往返转换:
assert math.isclose(t, f_to_c(c_to_f(t))), f"{t} F -> C -> F 转换失败!"
# 将摄氏度通过华氏度进行往返转换:
assert math.isclose(t, c_to_f(f_to_c(t))), f"{t} C -> F -> C 转换失败!"
if __name__ == "__main__":
test_conversions()
我们可以使用specialist
从命令行通过CPython运行此文件:
$ specialist conversions.py
脚本运行完成后,specialist
将打开一个网络浏览器并显示带注释的程序源代码:
绿色区域表示成功专门化的代码区域,而红色区域表示未成功的专门化(以"自适应"指令的形式)。根据成功和失败的比例,混合结果以绿-黄-橙-红渐变色表示。不包含任何尝试专门化的代码区域保持白色。
暂时聚焦于f_to_c
和c_to_f
,我们可以看到CPython无法专门化与32
的加法和减法。它目前不专门化混合float
和int
值之间的二元运算符,这正是这里的代码所做的。
然而,它可以专门化两个float
值之间的加法和减法!将32
替换为32.0
会导致成功的专门化(通过重新运行specialist
确认):
我们可以看到float
和int
的乘法也发生了类似的情况。一个选项可能是继续将常量值转换为float
:
然而,还有一个更好的选择!注意CPython根本没有尝试专门化除法(在可视化中保持白色)。我们可以通过稍微改变操作顺序来利用CPython的常量折叠优化,这允许我们的缩放因子(5 / 9
和9 / 5
)在编译时计算。当我们这样做时,CPython能够完全使用原生浮点运算来实现我们的转换器:
模式
和python
本身一样,specialist
可以以几种不同的方式运行代码。可以给它一个文件路径:
$ specialist spam/eggs.py foo bar baz
或一个模块名:
$ specialist -m spam.eggs foo bar baz
或一个命令:
$ specialist -c 'import spam; spam.eggs()' foo bar baz
它还有一个-t
/--targets
选项,用于支持在脚本完成后发现任意"目标"文件进行分析。如果运行的脚本与你想要可视化的代码不同,这很有用:
$ specialist --targets spam/eggs.py -c 'import uses_eggs; uses_eggs.run()'
可以使用"glob"样式的模式提供多个文件:
$ specialist --targets 'spam/**/*.py' -m pytest
Specialist 还可以将生成的 HTML 文件写入文件系统,而不是在浏览器中打开它们。要实现这一点,只需使用 -o
/--output
选项提供一个输出目录路径:
$ specialist --output ../report --targets 'spam/**/*.py' -m pytest
/home/brandtbucher/sketch/spam/__init__.py -> /home/brandtbucher/report/__init__.html
/home/brandtbucher/sketch/spam/_spammy.py -> /home/brandtbucher/report/_spammy.html
/home/brandtbucher/sketch/spam/eggs/__init__.py -> /home/brandtbucher/report/eggs/__init__.html
/home/brandtbucher/sketch/spam/eggs/_eggy.py -> /home/brandtbucher/report/eggs/_eggy.html
选项
-b
/--blue
使用蓝色(而不是绿色)来表示专门化的代码。一些用户可能会发现蓝紫红色渐变比默认的绿黄橙红色渐变更易于阅读。
-d
/--dark
在深色背景上使用浅色文本。一些用户可能会发现深色方案比默认的浅色方案让他们感觉更舒适。