普贤教程 › 进阶教程
性能与陷阱
PuXian 的性能问题几乎都能归到一句话:你在解释轨还是编译轨。搞清这一点,剩下的坑都是有解的。
实测:同一个循环,解释轨比编译轨慢约 500 倍
px
var t0 = now_ms() var sum = 0 for i in range(0, 100000): sum += i print("耗时 " + str(now_ms() - t0) + " ms")
| 执行方式 | 耗时 |
|---|---|
px run(解释轨) | 5182–5928 ms(三次) |
px run --fast | 9–10 ms |
px build 默认(VM 字节码轨) | 10–12 ms |
px build --c(C 轨) | 4–5 ms |
结论直接一点任何"循环里干活"的代码不要用解释轨跑生产。定时任务、cron 脚本、接口处理,一律
px build 或让 Ma 用 mode: fast 托管。坑一:逐像素绘图
gfx.set_px 内部是 c[2][y*w+x] = color:嵌套下标赋值每次都把内层列表复制一份再写回。132×44 的画布写 6400 次,解释轨实测 3.85 秒;同样的写入直接作用在 canvas_pixels(c) 取出的列表上只要 0.89 秒;编译轨(含 --fast)4 ms。
| 做法 | 解释轨 | 编译轨 |
|---|---|---|
set_px 写 6400 次 | 3.85 s | 4 ms |
直写 canvas_pixels | 0.89 s | <1 ms |
| 拼 SVG 字符串 | 0.09 s | 0.05 s |
图形优先用 SVG纯字符串拼装,解释轨也是毫秒级,体积还小(3 KB vs 28 KB PNG)。前端
<img src="data:image/svg+xml;base64,..."> 直接吃。坑二:bytes 是不可变的
px
var b = bytes("abc") var c = bytes_set(b, 0, 65) # 返回值是新对象 print(bytes_to_str(b)) print(bytes_to_str(c))
text
abc Abc
忘记接返回值,看起来"改了但没生效"。逐字节修改还有性能问题:每次 bytes_set 都复制一份,几十万次就是几秒。做法是攒成列表最后一次性拼。
坑三:大块写入会挂
| 写法 | 结果 |
|---|---|
write_all(fd, data) 一次写 >16 MiB | 卡死(实测 17 MB 挂住) |
分块 write(每块 1 MiB) | 正常 |
write_file(path, bytes) | 写成 <bytes N> 字面量,不是原内容 |
write_bytes(path, b) | 正确 |
坑四:字符串操作对二进制不安全
split、replace 这类字符串函数遇到 NUL 字节会截断。40 MB 的 zip 用 split 处理,实测被切成 108 个字符。二进制一律用 bytes_* 族。
坑五:除法返回浮点
px
var avg = 7 / 2 print(str(avg)) print(str(int(avg)))
text
3.5 3
要整数用 //;位运算(>> &)只吃 int,浮点会报错。
坑六:保留字撞车
| 写法 | 报错 |
|---|---|
var from = "a@b.com" | E2001 期望变量名 |
var in = 1 | E2001 期望变量名 |
def env(...)(遮蔽内建) | 无限递归 → 段错误,无提示 |
坑七:json_parse 会终止程序
px
var v = json_parse("坏输入") # 不可捕获,进程直接挂
一律 json_parse_opt。同理 int("abc")、hex_to_bytes 的畸形输入。处理外部数据前先校验。
坑八:print 就是 HTTP 响应
在 Ma 的 px_routes 里,print 写的是标准输出 = 响应体。处理中间打一行日志,客户端收到的是坏响应。日志走 /dev/stderr。
坑九:mode: auto 的首请求
Ma 的 px_routes 在 mode: auto 下,首次请求走解释轨(可能要 1 秒多),之后才用编译产物。用户感知是"第一次点特别慢"。对交互敏感的路由直接写 mode: fast。
性能优化清单
| 症状 | 先查什么 |
|---|---|
| 整体慢 | 是不是在跑解释轨 |
| 某个接口 1 秒+ | 是否有数据库连接建立、外部请求、发信 |
| CPU 100% 单核 | 循环里做字符串拼接 / 逐字节操作 |
| 内存持续涨 | 循环里 append 不释放;或 bytes 反复复制 |
| 偶尔慢一下 | 首次编译、GC、磁盘抖动 |
先测量再优化用
now_ms() 打点,先找出是"一次慢"还是"每次慢"。PuXian 里没有 profiler,打点日志就是最实用的手段。