news 2026/9/12 21:20:01

Python还能这么跑?不用改代码,性能飙升50倍

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python还能这么跑?不用改代码,性能飙升50倍

还能这么跑?不用改代码,性能飙升50倍

有段 脚本,平时跑一批对账文件要二十多分钟。

代码没改,SQL 没改,机器也没换,只把启动命令从:

python check_bill.py

换成:

pypy3 check_bill.py

第二次跑完,我第一反应不是高兴,是不太信。

这般性能提升实在是离谱至极, 离谱得我通常会率先怀疑三件事情: 其一, 是否数据未曾读取完备;其二, 是不是逻辑有所提前;其三, 会不会是缓存把结果给糊弄过去了。

所以我先加了几行很土的日志:

import time import os started = time.perf_counter rows = 0 bad_rows = 0 with open("bill_202406.log", "r", encoding="utf-8") as f: for line in f: rows += 1 parts = line.rstrip("\n").split("|") if len(parts) != 6: bad_rows += 1 continue user_id, order_no, amount, channel, status, sign = parts # 模拟线上老脚本里的校验逻辑,纯 Python 字符串和整数计算 seed = 17 for ch in order_no + channel + amount: seed = (seed * 131 + ord(ch)) & 0xFFFFFFFF if sign != hex(seed)[2:]: bad_rows += 1 cost = time.perf_counter - started print({ "pid": os.getpid, "rows": rows, "bad_rows": bad_rows, "cost": round(cost, 3), })

日志能对上:

{'pid': 31872, 'rows': 3000000, 'bad_rows': 1847, 'cost': 1268.442}

换 PyPy 后:

{'pid': 31940, 'rows': 3000000, 'bad_rows': 1847, 'cost': 24.931}

这地方就有意思了。

代码未曾变动, 结果照样相同, 所耗费的时间, 从原本二十多分钟, 大幅下降到二十五秒上下, 大约降低了五十倍。

这并非是, 忽然间就开窍了, 也不是说 PyPy 对于全部代码而言都这般厉害。它所适用的是一种极为特定的场景, 即存在大量纯粹的循环, 有关字符串的处理, 还有整数的计算, 逻辑会反复地执行, 并且代码路径比较稳定。

我以前见过不少 慢脚本,慢得很冤。

比如这种:

defpick_risk_orders(lines): risky = for line in lines: cols = line.strip.split(",") if len(cols) < 5: continue order_id = cols[0] user_level = cols[2] amount = int(cols[3]) city = cols[4] score = 0 if amount > 5000: score += 40 if user_level in ("new", "guest"): score += 20 for c in city: score += ord(c) % 7 if score >= 55: risky.append(order_id) return risky

这种代码在 里跑,慢不奇怪。

它的每一行, 都在进行对于执行的逐个解析, 每次进入循环, 都需要去查找变量, 调用函数, 拆解字符串, 转换为整数, 一旦业务数量越来越多, 数据规模越来越大, CPU 表面上呈现出忙碌的状态, 可实际上, 大量的时间均耗费在了解释器自身所产生的开销之上。

PyPy 不一样。

它具备 JIT, 代码运行一会儿之后, 它察觉到某一 段循环格外热, 就会将这段逻辑编译成更趋近于机器能直接去执行的事物。热循环越显著, 收益也就越大。

我这类脚本一般不先上来就改算法。

先看三个东西。

第一,慢在哪里。

python -m cProfile -s cumtime check_bill.py | head -30

如果前面全是业务函数,比如:

ncalls tottime cumtime function 3000000 411.23 913.54 parse_line 3000000 302.18 302.18 calc_sign 3000000 166.91 166.91 normalize_channel

这种就值得拿 PyPy 试一下。

第二,依赖干不干净。

PyPy 对于单纯的情况是很友善的, 然而要是你的项目当中存在大量的 C 扩展, 就像某些科学计算方面的库、图像相关的库、加密类的库, 那收益就未必如此了。并非是不能够运行, 而是需要先去进行验证。

我一般先这么扫一眼:

python - <<'PY' import pkg_resources suspects = for dist in pkg_resources.working_set: name = dist.project_name.lower if name in {"numpy", "pandas", "cryptography", "pillow", "lxml"}: suspects.append(dist.project_name) print("native-like packages:", suspects) PY

有这些包,不代表 PyPy 没戏,只是别拍脑袋上线。

第三,脚本是不是跑得够久。

PyPy存在提前预热所需的成本, 对于一个在0.3秒就结束运行的小脚本而言, 倘若换成它, 或许反而会变得更慢, 因为在JIT还没能够来得及开展工作的时候, 程序就已经完成运行并结束了。

所以我喜欢用这种方式做对比,别只跑一次:

import subprocess import time cmds = [ ("cpython", ["python", "check_bill.py"]), ("pypy", ["pypy3", "check_bill.py"]), ] for name, cmd in cmds: costs = for i in range(3): t1 = time.perf_counter subprocess.run(cmd, check=True) costs.append(round(time.perf_counter - t1, 3)) print(name, costs)

看第二次、第三次更有意义。

这里有个坑,我得单独说一下。

可别将“换 PyPy”视作性能优化的那种能解决一切问题的万能药。接口运行速度迟缓, 数据库运行速度迟缓, 网络传输速度迟缓, 文件 IO 操作进行得缓慢, 它可解决不了多少这类状况。

比如这种代码:

defsync_user(conn, api): users = conn.query("select id, mobile from user_pending limit 10000") for user in users: resp = api.post("/user/check", json={"mobile": user["mobile"]}) if resp["ok"]: conn.execute( "update user_pending set checked=1 where id=%s", (user["id"],) )

这个慢,多半不是 慢。

第一眼, 我会先查看接口耗时, 再看数据库执行计划, 接着看连接池, 然后轮到批量提交, 不会首先去更换解释器。你将它丢给PyPy, 也就是把循环那微乎其微的开销节省掉, 然而真正占据大头的部分还在外面呢。

但是, 要是属于这种离线清洗的情况, 还有规则匹配, 以及日志扫描, 再加上批量校验, 甚至是协议解析, PyPy极有可能会有惊喜出现。

再贴一个更接近线上小工具的写法。

defscan_nginx_error(path): stat = {} with open(path, "r", encoding="utf-8", errors="ignore") as f: for line in f: if" upstream timed out "notin line: continue api = "-" pos = line.find('"GET ') if pos < 0: pos = line.find('"POST ') if pos >= 0: left = line.find(" ", pos + 1) right = line.find(" ", left + 1) if left > 0and right > left: api = line[left + 1:right].split("?")[0] stat[api] = stat.get(api, 0) + 1 return sorted(stat.items, key=lambda x: x[1], reverse=True)[:20] if __name__ == "__main__": for api, cnt in scan_nginx_error("access.log"): print(cnt, api)

这种脚本, 写着顺手,但文件一到几十 GB,就开始磨人。

当然, 那你能够改成为awk, 可以借助Rust重新进行编写, 并且也能够运用Spark。

然而不少情形下仅仅是进行临时的排查, 实在是没什么必要将事情弄得那般严重。首先去换 PyPy 运行一次, 这成本最为低廉。

安装也不复杂。

# Ubuntu sudo apt install pypy3 # macOS brew install pypy3

跑之前看下版本:

pypy3 -V

虚拟环境可以单独建,别和原来的 环境混着来:

pypy3 -m venv .venv-pypy source .venv-pypy/bin/activate pip install -r requirements.txt

然后启动命令改一下:

pypy3 check_bill.py

严格说,这已经算“换运行时”了,不是玄学优化。

代码依旧是那一份代码, 然而运行它的解释器发生了更换。这恰似同一条 SQL, 于不同的数据库引擎之下, 在不同的执行计划之中, 其表现或许全然就不是同一个样子了。

我现在处理 慢脚本,大概会按这个顺序来:

先确认结果一致。

再用 看是不是纯 热循环。

然后用 PyPy 跑一版。

如果提升明显,就继续压依赖兼容性、内存、启动耗时。

如果提升不明显,再回头改算法、批处理、并发、IO。

不要一上来就重构。

有不少运行速度迟缓的代码并非编写水准欠佳, 仅仅是运行的方式存在问题。特别是那些年代久远的脚本, 没有人敢于去改动, 哪怕改动一行都担心会对账目产生影响。在这样的时候, “不更改代码”反倒是最为稳妥的着手点。

不过那句“性能飙升 50 倍”,别乱贴到所有 项目上。

适用于它的情况是, 处理CPU密集型任务, 执行单纯的循环操作, 运行执行时间较长, 所依赖之要素并不复杂。

若命中了这几个条件, PyPy着实会把人吓得不轻。要是没命中, 那就别强行吹嘘。去运行一下, 日志便能道出实情。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 21:19:52

jQuery

关键知识点速览DOM 操作与选择器&#xff1a;利用 $(#id) 或 $(.class) 快速抓取页面元素&#xff0c;比原生 JavaScript&#xff08;document.getElementById&#xff09;简洁得多。事件绑定&#xff1a;通过 $(#btn).click(function() { ... }) 轻松处理点击、提交等交互事件…

作者头像 李华
网站建设 2026/9/12 21:16:20

Clipcat 评测:适合 TikTok Shop 卖家的 AI 提示词资源库吗?

如果你在做 TikTok Shop&#xff0c;大概率见过这样的工作状态&#xff1a;选品表、素材盘、剪辑软件、AI 对话工具一个不少&#xff0c;但每天要做新视频时&#xff0c;团队还是会卡在第一步。 “这条该从什么角度讲&#xff1f;” “同一个保温杯&#xff0c;除了开箱还能拍什…

作者头像 李华
网站建设 2026/9/12 21:13:39

酒店门店 AI 数字人前台场景适配与落地指南

深夜的酒店大堂&#xff0c;往往只剩下前台微弱的灯光和偶尔传来的键盘敲击声。对于很多连锁品牌来说&#xff0c;夜间值守一直是个两难选择&#xff1a;安排专人成本高且难以保证整夜精神饱满&#xff0c;完全无人又担心客人突发需求无法响应。引入数字人前台本是为了破局&…

作者头像 李华
网站建设 2026/9/12 21:13:14

LangChain02 Agent核心双翼

LangChain02 Agent核心双翼核心目标&#xff1a;掌握 Tool Calling&#xff08;工具调用&#xff09; 和 RAG&#xff08;检索增强生成&#xff09;。这是让大模型“长出手脚”和“拥有外部大脑”的绝对核心&#xff0c;也是后续学习 LangGraph 的底层基石。 核心心法&#xff…

作者头像 李华