在 Python 中以每秒数十 GB 的速度解析 JSON
摘要
Daniel Lemire 演示了如何在无 GIL 的 Python 3.14 中,使用 fastsimdjson 的 map_many 多线程与惰性解析,在数秒内解析 7.6 GB 的 GH Archive JSON 数据,性能远超标准库 json、orjson 与 msgspec。
<p><a href="https://lobste.rs/s/iufma5/parsing_json_at_tens_gigabytes_per_second">评论</a></p>
查看缓存全文
缓存时间: 2026/10/12 15:47
# 在 Python 中以每秒数十 GB 的速度解析 JSON
来源:https://lemire.me/blog/2026/10/12/parsing-json-at-tens-of-gigabytes-per-second-in-python/
过去,Python 的并行能力受限于全局解释器锁(GIL)。同一时刻只有一个线程可以执行 Python 代码。Python 3.14 推出了自由线程版本(`python3.14t`),取消了全局解释器锁。现在多个线程可以同时运行 Python 代码了。
让我们选一个实际的问题。GitHub 公布了其公共仓库上发生的所有事件:推送、议题、Pull Request、星标等等。GH Archive(https://www.gharchive.org/)项目将这些事件收集为 JSON 格式,每行一个事件(NDJSON),每小时一个文件。我取了 2024 年 3 月 13 日的八个小时数据:解压后共 7.6 GB 的 JSON,220 万个事件。我想知道哪些编程语言获得了最多的新 Pull Request。因此,我按事件类型计数,并对每个打开的 Pull Request 查找其仓库所使用的语言。每个事件都是一个较大的 JSON 对象,平均约 3.4 kB:但我只需要其中几个字段。
这八个小时的结果是:共打开了 65,269 个 Pull Request,其中大多指向 TypeScript(11,565)、JavaScript(7,778)、Python(7,057)和 Java(4,668)仓库。约有 5,857 个指向没有标记语言的仓库。
使用 fastsimdjson(https://github.com/simdjson/fastpysimdjson),即我们的 simdjson Python 绑定库,代码如下所示。函数 `map_many` 会对每个事件调用我的函数,并使用我指定的线程数量。
```python
import collections, threading
import fastsimdjson
local = threading.local()
parts = []
def one_event(e):
acc = getattr(local, "acc", None)
if acc is None: # 该线程看到的第一个事件
acc = local.acc = (collections.Counter(), collections.Counter())
parts.append(acc)
t = e["type"]
acc[0][t] += 1
if t == "PullRequestEvent":
p = e["payload"]
if p["action"] == "opened":
acc[1][p["pull_request"]["base"]["repo"]["language"]] += 1
data = open("events.json", "rb").read()
fastsimdjson.map_many(one_event, data, format="lines", threads=64, lazy=True)
types, languages = collections.Counter(), collections.Counter()
for t, lang in parts:
types.update(t)
languages.update(lang)
```
每个线程维护自己独立的计数器。线程看到的第一个事件会创建一对计数器并将其追加到 `parts` 中。之后该线程上的事件更新同一对计数器。计数器中没有任何共享数据,因此线程之间无需互相等待。`map_many` 只在所有事件都被统计完毕后才返回。然后主线程遍历 `parts`,每线程一对,用 `Counter.update` 合并计数。事件类型不过几十种,语言约百来种:合并的开销与解析相比微不足道。
使用 `lazy=True` 时,我的函数接收到的是事件的懒惰视图而非字典:事件已被解析,但只有我实际访问的值才会被创建为 Python 对象。这种懒惰方式最早由 Tyler Kennedy(https://github.com/TkTech)在另一个 simdjson 绑定库 pysimdjson(https://github.com/TkTech/pysimdjson)中开发。
对比情况如何呢?我尝试了常见的对手:标准库(`json`)、orjson(https://github.com/ijl/orjson)和 msgspec(https://github.com/jcrist/msgspec)。msgspec 值得特别一提:它可以将 JSON 解码为类型化结构体,只声明你需要的字段,跳过其余部分。这与我们的懒惰视图非常相似,区别在于你必须提前声明 schema。我让 msgspec 享有这个优势。在每种情况下,我都验证了我们得到的计数完全一致。
我使用一台配备双路 Intel Xeon Gold 6548N 处理器(64 核)的服务器,Python 3.14.6,以及各库的当前版本(fastsimdjson 0.6.0,orjson 3.13.0,msgspec 0.22.0)。数据在计时开始前已解压到内存中。我报告七次运行中最好的一次,对于较慢的方法只报告一次运行结果:单线程解析器,以及标准库在 64 线程下的表现。先看单核性能。
**单核**
| 方法 | GB/s |
| --- | --- |
| `json`(标准库) | 0.18 |
| orjson | 0.42 |
| fastsimdjson `map_many` | 0.63 |
| msgspec(类型化结构体) | 1.29 |
| fastsimdjson `map_many`(lazy) | 2.13 |
标准库需要 42 秒才能遍历这八个小时的数据。使用 fastsimdjson 和懒惰视图,只需 3.6 秒。注意那些将所有内容都实例化的方法(`json`、orjson 以及不使用懒惰视图的 fastsimdjson)速度很慢:大部分时间都花在创建我们马上丢弃的 Python 对象上。
现在让我们使用全部 64 核,仍然在单个进程中。在自由线程 Python 上,`json` 和 msgspec 使用线程池,fastsimdjson 使用 `map_many(..., threads=64)`。线程共享输入数据。
**64 核**
| 方法 | GB/s |
| --- | --- |
| `json`(标准库),64 线程 | 3.17 |
| msgspec(类型化结构体),64 线程 | 24.6 |
| fastsimdjson `map_many`,64 线程 | 29.8 |
| fastsimdjson `map_many`(lazy),64 线程 | 65.7 |
我们在一个 Python 进程中以约 66 GB/s 的速度在约 0.12 秒内处理完 7.6 GB 的 JSON。这比单核快了 31 倍。它甚至比使用 64 线程的 msgspec(25 GB/s)还要快。
所以,你能在 Python 中以每秒数十 GB 的速度解析 JSON 吗?可以。
fastsimdjson 0.6.0 已在 PyPI(https://pypi.org/project/fastsimdjson/)上发布。我的基准测试代码在此(https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2026/10/pyjson)。
相似文章
@shubh6200: 要了解如何处理海量文件,请阅读 @geofflangdale 和 @lemir 的《Parsing Gigabytes of JSON per Second》…
该论文介绍了 simdjson,这是第一个能够在单核上使用 SIMD 指令每秒处理数 GB 数据的验证性 JSON 解析器,相比 RapidJSON 等现有解析器实现了显著的加速。
使用计算着色器在GPU上进行并行JSON解析
slurpjson是一个Rust库,它完全在GPU上使用wgpu计算着色器解析JSON,将解析过程分解为并行前缀扫描,用于研究目的。
在ARM处理器上使用SVE2加速JSON解析
文章介绍了如何利用ARM处理器上的SVE2 SIMD指令,通过字符匹配指令来优化simdjson库中的JSON解析,从而实现更快的性能。
使用C++26静态反射在编译时解析JSON
C++26的#embed和静态反射,结合simdjson库,允许在编译时解析JSON,将配置文件转化为编译时常量,无运行时开销。
JSON 拆解技术如何让 Logfire 查询速度提升 1000 倍
Pydantic 的技术博客介绍 Logfire 通过 dynamic shredding(动态拆解半结构化 JSON 属性数据)技术实现部分查询提速 1000 倍,原本 30 秒超时的查询现在不到一秒即可完成,将于 2026 年 2 月向所有客户推出。