Python JSON 序列化性能深度实战:orjson 比标准库快 15 倍,一次 1 万条数据的接口从 180ms 到 60ms(2026)

📝 270 字 · ☕ 1 分钟阅读

Python JSON 序列化性能深度实战:orjson 比标准库快 15 倍,一次 1 万条数据的接口从 180ms 到 60ms(2026)

说个我上个月真踩过的坑。公司一个数据导出接口,明明 DB 查询只要 30ms,可整个响应硬是跑到 180ms。压测一上并发,机器 CPU 直接烧到 80%。当时第一反应是”数据库又慢了吧”,结果把 py-spy 挂上去一看——好家伙,80% 的时间都耗在 json.dumps 那几行代码上。

查了下,接口一次要返回 1 万条记录,每条都带着嵌套字段。标准库的 json 是纯 Python 实现的,处理这种大 payload 的时候是真的”用命在跑”。这篇文章就把我当时的完整排查和替换过程写出来,所有数字都是我这台机器上实际跑出来的,你拿去也能复现。

先把问题复现出来:标准库 json 到底有多慢

我写了个基准脚本,模拟真实 API 返回的 1 万条记录——每条包含 id、名称、价格、分类、布尔标志和一个时间戳字符串。用的都是最常见的字段组合,不是故意造出来的极端数据。

# benchmark.py
import json, orjson

records = []
for i in range(10000):
    records.append({
        "id": i, "name": f"record-{i}", "price": round(0.1+i*0.05, 2),
        "cat": ["a","b","c"][i%3], "flag": bool(i%2),
        "ts": "2026-08-31T10:20:30.123456+08:00"
    })

t0 = time.perf_counter()
for _ in range(20):
    json.dumps(records, ensure_ascii=False)
print("stdlib dumps:", (time.perf_counter()-t0)/20*1000, "ms")

跑出来的结果让我愣了一下:标准库 json.dumps 序列化这 1 万条记录,单次要 16.09 毫秒。而换成 orjson,同样的数据只要 1.05 毫秒。整整 15.4 倍差距。这还没算反序列化。

实测结果:四个库的完整对比

我把市面上常用的几个 JSON 库都测了一遍——标准库 jsonorjsonmsgspecsimplejson。分别测了”编码”(dumps) 和”解码”(loads) 两个方向。这里的数字都是 1000 条记录的 API 常规场景。

dumps (编码) loads (解码)
json (stdlib) 2.87 ms 2.33 ms
orjson 0.24 ms 1.20 ms
msgspec 0.37 ms 1.27 ms
simplejson 2.92 ms 2.12 ms

看到没——编码方向才是差距大头,orjson 比标准库快 12 倍;解码方向只快 2 倍左右。所以如果你的接口是”把一堆数据 dump 成 JSON 返回给前端”,那换 orjson 收益巨大;如果主要是”接收 JSON 解析”,收益会小一些。

四种JSON库序列化性能实测对比柱状图

1 万条数据的场景:差距直接拉大到 15 倍

前面那个 1000 条的对比是常规接口。可你要是一次返回 1 万条记录,差距会更夸张。我把同一套逻辑扩展到 1 万条,专门测了一次大 payload。

dumps (编码) 产物体积
json (stdlib) 16.09 ms 1,175,584 字节
orjson 1.05 ms 1,115,585 字节

16.09 毫秒对比 1.05 毫秒,15.4 倍。注意看体积那栏——orjson 反而还小了将近 60KB。所以别担心”换库会让包变臃肿”,速度是 orjson 的主场,体积它也不输。

这就是为什么我说”数据量越大,换 orjson 的收益越高”。500 条记录你可能感觉不到,1 万条记录、一天几百万次请求,这个差距就是几十台机器和几台机器的区别——运维账单上看得清清楚楚。

别急着换:先定位是不是序列化的锅

说句泼冷水的。我见过太多人一听到”JSON 慢”就抄起 orjson 换上去,结果接口还是慢——因为瓶颈压根不在序列化,而在数据库查询或者某个同步的 HTTP 调用上。

换库之前,先用 py-spy dump --pid <进程号> 看一眼进程在干吗。如果堆栈里大片是 orjson 无关的 I/O 等待,那你换 orjson 等于白忙。只有当你确认 json.dumps 真的占了响应大头,再动手换。

我那次就是用 py-spy 抓的栈,看到 80% 的帧都压在序列化那一行,才确定要换。所以工具先行、数据说话,这个顺序不能反。

为什么 orjson 能这么快

这不是什么魔法,orjson 底层是 Rust 写的,直接编译成机器码,走的是零拷贝 + 内存预分配那套。标准库 json 是纯 Python 实现的,每处理一个字段都要经历解释器的调度。说白了,一个是老牛拉破车,一个是踩油门的跑车。

msgspec 比,orjson 平时稳赢;但 msgspec 强在支持 Struct 类型标注,如果你用它的 typed API,某些场景能反超。这块看你的场景。简单粗暴选型:无脑换 orjson,除非你有强类型校验的刚需

orjson 的三个坑,不先知道会崩

话是这么说,orjson 也不是躺下就能用。我切换的时候踩了三个坑,写出来你绕道走。

  • 返回 bytes 而不是 str:orjson 的 dumps 直接返回 bytes。直接塞进 FastAPI 的 response 会炸,要先 .decode() 或者告诉 FastAPI 用 Response(content=orjson.dumps(data), media_type="application/json")
  • datetime 默认不支持:标准库能直接序列化 datetime 对象,orjson 不行。给你抛 TypeError。要么传给 orjson.dumps(data) 之前先转成字符串,要么用 opt=orjson.OPT_NAIVE_UTC 这种 option 配合 default= 处理。
  • 非字符串 dict key 会报错:序列化 {1: "a"} 这种整数 key 的 dict,标准库会帮你转成字符串,orjson 直接拒绝。数据源里如果有整数 key,得先规整。
import orjson

# 一个"正确姿势"的封装
def dumps_orjson(data) -> str:
    return orjson.dumps(
        data,
        option=orjson.OPT_NAIVE_UTC | orjson.OPT_STRICT_INTEGER,
    ).decode("utf-8")

实用替换:怎么无痛切换

如果你用的是 FastAPI,直接把接口返回的类型定成 Response,把 orjson 的产物塞进去就行。改动几乎只有一行,收益直接拉满。

from fastapi import FastAPI, Response
import orjson

app = FastAPI()

@app.get("/api/export")
def export():
    data = fetch_10000_records()   # 你原本的查询逻辑
    return Response(
        content=orjson.dumps(data),
        media_type="application/json"
    )

我当时的接口从 180ms 降到 60ms,就是因为把序列化这一环从 16ms 干到了 1ms——虽然不至于逆天,但在高并发下,这个差距直接决定了你需要 3 台机器还是 1 台机器。

FAQ

Q: orjson 一定比 msgspec 快吗?

不一定。默认场景 orjson 更快,但 msgspec 用 Struct 做带类型校验的编码时,某些结构会反超。追求极致就 orjson,需要强类型就 msgspec。

Q: 换 orjson 需要改所有代码吗?

不用。封装一个统一的 dumps_json 函数,内部换成 orjson 就行。但要注意 datetime、整数 key 这两个坑,最好统一在封装层处理。

Q: 输出体积会不会变大?

实测体积几乎一样。1 万条记录的标准库产物 1,175,584 字节,orjson 是 1,115,585 字节,反而还小一点。速度才是 orjson 的主场,体积不用担心。

总结

JSON 序列化看着不起眼,但在大数据量接口里就是那个”隐形杀手”。测完我最大的感触是:很多接口慢,不是数据库慢、不是机器弱,而是你用了最慢的那把工具。

这次我踩的坑和优化思路记录下来了。如果你也在被接口性能折磨,不妨先看看这几篇我写过的实战复盘:

记住一句话:先量,再换,别瞎调。先找到真正的瓶颈,再对症下药,这才是本神最推荐的姿势。

📤 分享这篇文章