目录
为 Python 选择更快的 JSON 库
你使用 JSON 越多,就越有可能遇到 JSON 编码或解码成为性能瓶颈的情况。Python 的内置库并不差,但有多个更快的 JSON 库可供选择:你该如何选择使用哪一个呢?
事实上,并没有一个放之四海而皆准的答案,也没有一个最快的 JSON 库能够适用于所有场景:
- 对于不同的人来说,“快速的 JSON 库”意味着不同的东西,因为他们的使用模式不同。
- 速度并不是一切——你可能还关心其他方面,比如安全性和可定制性。
因此,为了帮助你选择最适合你需求的快速 JSON 库,我想分享一下我为 Python 选择快速 JSON 库的过程。你可以使用这个过程来选择最适合你特定需求的库:
- 确保确实存在问题。
- 定义基准测试。
- 根据额外需求进行筛选。
- 对剩余的候选库进行基准测试。
第一步:你真的需要一个新的 JSON 库吗?
仅仅因为你使用了 JSON,并不意味着它就是一个相关的性能瓶颈。在你花时间思考哪个JSON 库之前,你需要一些证据表明 Python 的内置 JSON 库确实在你的特定应用中存在问题。
我了解到,JSON 编码占用了生成消息时 CPU 时间的约 25%。我能获得的最大加速是运行速度提高 33%(如果 JSON 编码时间降为零),但这是一个相当大的时间块,迟早会排到优化列表的顶部。
第二步:定义基准测试
如果你查看各种 JSON 库的基准测试页面,它们会讨论它们在不同消息上的表现。这些消息并不一定与你的使用情况相对应。通常情况下,它们测量的是非常大的消息,而在我的案例中,我关心的是小消息。
因此,你需要提出一些符合你特定使用模式的测量标准:
- 你关心编码、解码,还是两者都关心?
- 你使用的是小消息还是大消息?
- 典型的消息是什么样的?
在我的案例中,我主要关心编码小消息,特别是由 Eliot 生成的日志消息的结构。我根据一些真实的日志提出了以下示例消息:
{
"timestamp": 1556283673.1523004,
"task_uuid": "0ed1a1c3-050c-4fb9-9426-a7e72d0acfc7",
"task_level": [1, 2, 1],
"action_status": "started",
"action_type": "main",
"key": "value",
"another_key": 123,
"and_another": ["a", "b"],
}
第三步:根据额外需求进行筛选
性能并不是一切——你可能还关心其他方面。在我的案例中:
- 安全性/抗崩溃性:日志消息可能包含来自不受信任来源的数据。如果 JSON 编码器在遇到错误数据时崩溃,这对可靠性和安全性都不好。
- 自定义编码:Eliot 支持 JSON 编码的自定义,因此你可以序列化其他类型的 Python 对象。一些 JSON 库支持这一点,而另一些则不支持。
- 跨平台:能够在 Linux、macOS 和 Windows 上运行。
- 维护性:我不想依赖一个没有积极支持的库。
我考虑的库包括 orjson、rapidjson、ujson 和 hyperjson。
根据上述标准,我筛选掉了一些库:
- 在我最初撰写这篇文章时,
ujson
有许多关于崩溃的 bug 报告,并且自 2016 年以来没有发布新版本。虽然现在看起来它又开始维护了,但我还没有重新审视它。 hyperjson
只有 macOS 的包,而且总体上看起来非常不成熟。现在他们只推荐使用orjson
。
第四步:基准测试
最终的两个候选者是 rapidjson
和 orjson
。我运行了以下基准测试:
import time
import json
import orjson
import rapidjson
m = {
"timestamp": 1556283673.1523004,
"task_uuid": "0ed1a1c3-050c-4fb9-9426-a7e72d0acfc7",
"task_level": [1, 2, 1],
"action_status": "started",
"action_type": "main",
"key": "value",
"another_key": 123,
"and_another": ["a", "b"],
}
def benchmark(name, dumps):
start = time.time()
for i in range(1000000):
dumps(m)
print(name, time.time() - start)
benchmark("Python", json.dumps)
# orjson 只输出字节,但我们通常需要 Unicode:
benchmark("orjson", lambda s: str(orjson.dumps(s), "utf-8"))
benchmark("rapidjson", rapidjson.dumps)
结果如下:
$ python jsonperf.py
Python 4.829106330871582
orjson 1.0466396808624268
rapidjson 2.1441543102264404
即使需要额外的 Unicode 解码,orjson
仍然是最快的(在这个特定的基准测试中!)。
当然,总有一些权衡。orjson
的用户比 rapidjson
少(比较 orjson PyPI 统计数据 和 rapidjson PyPI 统计数据),而且没有 Conda 包,所以我必须自己为 Conda-forge 打包。但它确实快得多。