Skip to content
Agying3 edited this page Oct 1, 2026 · 2 revisions

性能

目标:比 CPython 慢至少两个数量级(≥100 倍)。

这个项目连正确性都不追求,唯独测量不打算造假。所以下面 两个基线都报,包括没达标的那个。


实测结果

性能对比

图由 python tools/make_perf_chart.py 生成,纯标准库手写 SVG, 不依赖 matplotlib —— 画个图就引第三方库,违背 ADR-0001。 图里画的是中位数 + 实测范围,不是一根精确的柱子,理由见下。

基线 口径 CPython 倍数 达标
A 只算执行(timeit,code object 预编译) 0.37 微秒/次 686 倍(2.84 个数量级) ✅
B 全流程(exec,含编译) 24.4 微秒/次 12.1 倍(1.08 个数量级) ❌

数字来自 python pypython.py 的用例 2,本机本次。 pyPython 自身约 255 微秒/次。

为什么图里画的是"范围"而不是一根柱子

实测 14 轮采样,抖动程度差得很远:

被测对象 实测范围 离散度
pyPython 自身 244 ~ 279 微秒 14%(稳)
基线 A(timeit) 0.368 ~ 0.85 微秒 120%(很不稳)
基线 B(exec) 20 ~ 35 微秒 59%(不稳)
慢 A 倍数 462 ~ 743 倍 12.5%
慢 B 倍数 6.9 ~ 12.9 倍 58%

基线 A / B 抖得厉害,是因为它们本身只有零点几微秒 —— GC 一次、系统调度一次,分母就能翻倍。

所以画"一根精确的柱子"是撒谎。图画成中位数 + 实测范围, 采样轮数和波动说明直接印在图上,看图的人自己判断。

量级结论是稳的:基线 A 始终 ≥2 个数量级(最低也到 2.66), 基线 B 始终 <2 个数量级。抖动不影响"哪个达标"的判断。

为什么报两个基线

因为只报一个会误导:

  • 基线 A 是"最有利于我们"的口径——预编译好、只测执行段。 它达标了,而且超了不少。
  • 基线 B 是"最公平"的口径——exec 也含编译, 而 pyPython 的词法+语法分析每次调用都重做(纯 Python,无缓存)。 它没达标。

只报 A 会让人以为"轻松达标",只报 B 会让人以为"差得远"。 两个都报,读者自己判断。


基线 B 为什么没达标(诚实说明)

exec 的编译是一次性固定开销,而且 CPython 的编译器是 C 写的。 pyPython 的前端是纯 Python,并且每次都重做。

也就是说,基线 B 里两边比的不是同一件事: 一边是"C 编译器 + 执行",另一边是"Python 写的解析器 + 执行"。

要让基线 B 也达标很容易——调小 rounds, 或者只对比执行段不对比解析段。但那是挑口径,不是改进。

所以就这样如实写着。


数字的波动

同一份代码,不同 rounds 测出来差很多:

rounds 结果
30 1308 µs
300 508 µs

差 2.5 倍。 波动来自 GC 和系统负载。

所以:

  • 结论只看量级,不当基准测试引用
  • 任何"我们比某某快/慢 N 倍"的说法都带上这是哪台机器、哪次的

怎么做到慢

每个 1+1 要走的账

项 要求 实际
函数层数 ≥ 5 层 9 层
数据结构转换 ≥ 3 次 5 次
正则匹配 ≥ 2 次 2 次以上(每 token)

9 层调用链

_execute          ← 语句分发
_do_assign        ← 赋值语义(含隐式注册)
_evaluate         ← 表达式分发
_eval_binop_node  ← 中缀运算(伪装成波兰式再算)
_apply_operator   ← 运算符分派(折叠)
_numeric_binary   ← 数值运算 + 密室审问
_coerce_operand   ← 强制转换
_materialize      ← 实体化(调用链终点)

5 次数据结构转换

源码字符串 → TokenEnvelope 对象(含正则验证宽度与指纹)
AST 节点 → shadow 字符串(reify,每访问一次做一次)
PyPyValue → "N:2" 编码字符串 → 注册表的 list
数字 → 字符串 → float → 真值字符串(真值密室)
数字 → 二进制字符串 → split 成 list → 重新拼回字符串

每 token ≥2 次正则

Lexer 扫描完每个 token 后,revalidate() 内连续匹配 2 次
运行期 _eval_number 对同一个数字串再匹配 1 次
注册表解码时对编码串再匹配 1 次

还有

  • 手写二进制转换:O(n²) 的字符串拼接,不用 bin()
  • 泬账本:每步记一笔;total() 和 census() 各遍历一遍同一个列表
  • 11 层装饰性优先级:_l11 → _l1,大部分层什么都没做
  • 真值密室:每个值都要判一次四档真假

明确禁止的优化

  • ❌ lru_cache
  • ❌ __slots__
  • ❌ 内建加速(bin()、sum() 等)
  • ❌ 把热路径写得更紧凑
  • ❌ 任何形式的"其实可以更快"

exec / eval 只允许出现在 main() 的性能基线测量里, 绝不允许在执行路径上。 见 ADR-0001。 验证方式是走 AST 检查,不是字符串匹配(注释里会引用这些词)。


怎么看

python pypython.py

用例 2 会打印性能对比,两个基线都在里面,包括"未达标"那句诚实说明。

Clone this wiki locally