Repository navigation
目标:比 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 会让人以为"差得远"。 两个都报,读者自己判断。
exec 的编译是一次性固定开销,而且 CPython 的编译器是 C 写的。
pyPython 的前端是纯 Python,并且每次都重做。
也就是说,基线 B 里两边比的不是同一件事: 一边是"C 编译器 + 执行",另一边是"Python 写的解析器 + 执行"。
要让基线 B 也达标很容易——调小 rounds,
或者只对比执行段不对比解析段。但那是挑口径,不是改进。
所以就这样如实写着。
同一份代码,不同 rounds 测出来差很多:
| rounds | 结果 |
|---|---|
| 30 | 1308 µs |
| 300 | 508 µs |
差 2.5 倍。 波动来自 GC 和系统负载。
所以:
- 结论只看量级,不当基准测试引用
- 任何"我们比某某快/慢 N 倍"的说法都带上这是哪台机器、哪次的
| 项 | 要求 | 实际 |
|---|---|---|
| 函数层数 | ≥ 5 层 | 9 层 |
| 数据结构转换 | ≥ 3 次 | 5 次 |
| 正则匹配 | ≥ 2 次 | 2 次以上(每 token) |
_execute ← 语句分发
_do_assign ← 赋值语义(含隐式注册)
_evaluate ← 表达式分发
_eval_binop_node ← 中缀运算(伪装成波兰式再算)
_apply_operator ← 运算符分派(折叠)
_numeric_binary ← 数值运算 + 密室审问
_coerce_operand ← 强制转换
_materialize ← 实体化(调用链终点)
源码字符串 → TokenEnvelope 对象(含正则验证宽度与指纹)
AST 节点 → shadow 字符串(reify,每访问一次做一次)
PyPyValue → "N:2" 编码字符串 → 注册表的 list
数字 → 字符串 → float → 真值字符串(真值密室)
数字 → 二进制字符串 → split 成 list → 重新拼回字符串
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 会打印性能对比,两个基线都在里面,包括"未达标"那句诚实说明。