2020 Google开发者大会重磅开幕 了解详情

每秒解析千兆字节的JSON解析器开源,秒杀一大波解析器!

2019 年 2 月 25 日

每秒解析千兆字节的JSON解析器开源,秒杀一大波解析器!

近日,GitHub 开源了一 JSON 解析器 simdjson,通过与其他常用解析器的对比实验,结果显示,simdjson 的解析速度达到 2.2GB/s,远远秒杀其他解析器,在下文中,我们将为大家详细介绍 simdjson。以下全文为 simdjson 在 GitHub 上的文档。


JSON 文档在互联网上无处不在,服务器花费大量时间来解析这些文档。我们希望在进行完全验证(包括字符编码)的同时尽可能使用常用的 SIMD 指令来加速 JSON 的解析。


一些性能结果


相比最先进的解析器(如 RapidJSON),我们可能使用四分之一或更少的指令,也只有 sajson 的一半。据我们所知,simdjson 是第一个在商用处理器上以每秒千兆字节速度运行的完全验证 JSON 解析器。



在 Skylake 处理器上,各种解析器解析 twitter.json 文件的速度(以 GB/s 为单位)如下所示。



基本要求


  • 通过Visual Studio 2017或更高版本支持Linux、macOS以及Windows等平台;

  • 带有AVX2的处理器;

  • 支持最近的C++编译器(例如,GNU GCC或LLVM CLANG或Visual Studio 2017),我们假设是C++ 17,GNU GCC 7或更高版本,或者LLVM的clang 6或更高版本;

  • 提供一些基准测试脚本,可以是bash和其他常用的实用命令程序,但是是可选的。


许可


代码采用 Apache License 2.0 许可。


在 Windows 下,我们使用 windows/dirent_portable.h 文件(在我们的库代码之外)构建了一些工具:基于自由的 MIT 许可。


代码示例


#include "simdjson/jsonparser.h"

/...

const char * filename = ... //

// use whatever means you want to get a string of your JSON document
std::string_view p = get_corpus(filename);
ParsedJson pj;
pj.allocateCapacity(p.size()); // allocate memory for parsing up to p.size() bytes
bool is_ok = json_parse(p, pj); // do the parsing, return false on error
// parsing is done!
// You can safely delete the string content
free((void*)p.data());
// the ParsedJson document can be used here
// js can be reused with other json_parse calls.

复制代码


如果你不介意为每个新的 JSON 文档分配内存,也可以使用更简单的 API:


#include "simdjson/jsonparser.h"

/...

const char * filename = ... //
std::string_view p = get_corpus(filename);
ParsedJson pj = build_parsed_json(p); // do the parsing
// you no longer need p at this point, can do aligned_free((void*)p.data())
if( ! pj.isValid() ) {
// something went wrong
}

复制代码


用法


简单的头文件


头文件可以看一下代码库的“singleheader”,用法可以看一下“amalgamation_demo.cpp”文件。这里不要求使用特定的构建系统:只需要将文件复制到项目中的路径中即可。然后,你就可以包含它们:


#include <iostream>
#include "simdjson.h"
#include "simdjson.cpp"
int main(int argc, char *argv[]) {
const char * filename = argv[1];
std::string_view p = get_corpus(filename);
ParsedJson pj = build_parsed_json(p); // do the parsing
if( ! pj.isValid() ) {
std::cout << "not valid" << std::endl;
} else {
std::cout << "valid" << std::endl;
}
return EXIT_SUCCESS;
}

复制代码


注意:在某些环境中,可能需要预编译 simdjson.cpp,而不是包含它。


在 Linux 或 macOS 等平台上使用旧版 Makefile


要求:最近的 clang(或 gcc)和 make。我们建议至少使用 GNU GCC/G ++ 7 或 LLVM clang 6,Linux 或 macOS 系统。


测试:


make
make test

复制代码


运行基准测试:


make parse
./parse jsonexamples/twitter.json

复制代码


在 Linux 上,parse 命令提供了性能计数器的详细分析。


运行其他作为比较的基准测试(使用其他解析器):


make benchmark

复制代码


使用 Linux 或 macOS 等平台上的 CMake


要求:需要最新版本的 cmake,在 macOS 上,安装 cmake 的最简单方法可能是使用 brew。


brew install cmake

复制代码


你需要一个像 clang 或 gcc 这样的新版编译器。我们建议至少使用 GNU GCC/G ++ 7 或 LLVM clang 6。例如,你可以使用 brew 安装最新的编译器:


brew install gcc@8

复制代码


可选:你需要通过设置 CC 和 CXX 变量告诉 cmake 你希望使用哪个编译器。在 bash 中,你可以使用 export CC = gcc-7 和 export CXX = g+±7 等命令。


构建:在项目代码库中执行以下命令:


mkdir build
cd build
cmake ..
make
make test

复制代码


CMake 将会构建出一个库。默认情况下,它构建的是一个共享库(例如,Linux上的libsimdjson.so)。


你可以构建一个静态库:


mkdir buildstatic
cd buildstatic
cmake -DSIMDJSON_BUILD_STATIC=ON ..
make
make test

复制代码


在某些情况下,你可能希望指定编译器,尤其是当系统默认编译器太旧的情况下。你可以按以下步骤操作:


brew install gcc@8
mkdir build
cd build
export CXX=g++-8 CC=gcc-8
cmake ..
make
make test

复制代码


通过 Visual Studio 在 Windows 上使用 CMake


我们假设你拥有一台至少装有 Visual Studio 2017 的普通 Windows PC,并支持 AVX2 的 x64 处理器(2013 Haswell 或更高版本)。


  • 从GitHub获取simdjson代码,例如,使用GitHub Desktop克隆它。

  • 安装CMake。在安装时,请确保可以从命令行使用cmake。请选择最新版本的cmake。

  • 在simdjson中创建一个子目录,例如VisualStudio。

  • 在shell中转到这个新创建的目录。

  • 在shell中键入cmake -DCMAKE_GENERATOR_PLATFORM=x64 …(或者,如果要构建DLL,可以使用命令行cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF …)。

  • 最后一个命令在新创建的目录(例如simdjson.sln)中创建了一个Visual Studio解决方案文件。在Visual Studio中打开这个文件。你现在应该能够构建项目并运行测试。例如,在“Solution Explorer”窗口中,右键单击“ALL_BUILD”,并选择“Build”。要测试代码,仍然在Solution Explorer窗口中,选择RUN_TESTS,再选择Build。


工具


  • json2json mydoc.json解析文档,构造模型,然后将结果输出到标准输出。

  • json2json -d mydoc.json解析文档,构造模型,然后将模型输出到标准输出。格式在随附的文件tape.md中有描述。

  • minify mydoc.json缩小JSON文档,将结果输出到标准输出。缩小意味着删除不必要的空格。


范围


我们提供了一个非常快的解析器。它根据各种规格对输入进行完全的验证。解析器会构建一个不可变(只读)的 DOM(文档对象模型),供后续访问。


为了简化工程,我们做了一些假设。


  • 支持UTF-8(以及ASCII),没有别的(没有Latin,没有UTF-16)。我们不认为这是一个真正的限制,因为我们不认为会有哪个严肃的应用程序需要在没有ASCII或UTF-8编码的情况下处理JSON数据。

  • 我们将字符串存储为以NULL作为终止符的C字符串。因此,我们假设字符串中不包含NULL字符。

  • 我们假设支持AVX2,这在AMD和英特尔生产的所有最新主流x86处理器中都可用。不支持非x86处理器,尽管我们可以支持。我们计划支持ARM处理器。

  • 如果发生故障,我们只会报告故障,而不会指出问题的性质。

  • 在规范允许的情况下,我们允许对象内存在重复的key。

  • 性能针对跨越几千字节到几兆字节的JSON文档进行了优化:解析很多小型JSON文档和一个大JSON文档的性能问题是不一样的。


我们的目标不是要提供通用的 JSON 库。像 RapidJSON 这样的库不仅提供了解析功能,它还可以用来生成 JSON,并提供了各种其他方便的功能。我们只解析文档。


特性


  • 不需改输入的字符串。(像sajson和RapidJSON这样的解析器使用输入字符串作为缓冲区。)

  • 将整数和浮点数解析为单独的类型,这样可以支持[-9223372036854775808,9223372036854775808]区间的64位整数,就像Java的long或C/C++的long long。在区分整数和浮点数的解析器中,并非所有解析器都支持64位整数。(例如,sajson不支持包含大于或等于2147483648整数的JSON文件。FreeJSON将长整数解析为浮点数。)当我们无法将整数表示为带符号的64位值时,我们就拒绝解析JSON文档。

  • 在解析过程中进行完整的UTF-8验证。(像fastjson、gason和dropbox json11这样的解析器不会进行UTF-8验证。)

  • 完全验证数字。(像gason和ultranjson这样的解析器会接受[0e+]这样的数字。)

  • 验证字符串内容中的未转义字符。(像fastjson和ultrajson这样的解析器接受字符串中未转义的换行符和制表符。)


架构


解析器分三个阶段:


  • 阶段1,(查找标记)快速标识结构元素、字符串等。我们在这个阶段验证UTF-8编码。

  • 阶段2,(结构构建)构建排序的“树”(物化为磁带),以方便访问数据。我们在这个阶段解析字符串和数字。


访问已解析的文档


以下是将解析后的 JSON 转储回字符串的代码示例:


ParsedJson::iterator pjh(pj);
if (!pjh.isOk()) {
std::cerr << " Could not iterate parsed result. " << std::endl;
return EXIT_FAILURE;
}
compute_dump(pj);
//
// where compute_dump is :

void compute_dump(ParsedJson::iterator &pjh) {
if (pjh.is_object()) {
std::cout << "{";
if (pjh.down()) {
pjh.print(std::cout); // must be a string
std::cout << ":";
pjh.next();
compute_dump(pjh); // let us recurse
while (pjh.next()) {
std::cout << ",";
pjh.print(std::cout);
std::cout << ":";
pjh.next();
compute_dump(pjh); // let us recurse
}
pjh.up();
}
std::cout << "}";
} else if (pjh.is_array()) {
std::cout << "[";
if (pjh.down()) {
compute_dump(pjh); // let us recurse
while (pjh.next()) {
std::cout << ",";
compute_dump(pjh); // let us recurse
}
pjh.up();
}
std::cout << "]";
} else {
pjh.print(std::cout); // just print the lone value
}
}

复制代码


下面的函数将找出所有的 user.id 整数:


void simdjson_traverse(std::vector<int64_t> &answer, ParsedJson::iterator &i) {
switch (i.get_type()) {
case '{':
if (i.down()) {
do {
bool founduser = equals(i.get_string(), "user");
i.next(); // move to value
if (i.is_object()) {
if (founduser && i.move_to_key("id")) {
if (i.is_integer()) {
answer.push_back(i.get_integer());
}
i.up();
}
simdjson_traverse(answer, i);
} else if (i.is_array()) {
simdjson_traverse(answer, i);
}
} while (i.next());
i.up();
}
break;
case '[':
if (i.down()) {
do {
if (i.is_object_or_array()) {
simdjson_traverse(answer, i);
}
} while (i.next());
i.up();
}
break;
case 'l':
case 'd':
case 'n':
case 't':
case 'f':
default:
break;
}
}

复制代码


深度比较


如果你想了解各种解析器如何验证给定的 JSON 文件:


make allparserscheckfile
./allparserscheckfile myfile.json

复制代码


性能比较:


make parsingcompetition
./parsingcompetition myfile.json

复制代码


更广泛的比较:


make allparsingcompetition
./allparsingcompetition myfile.json

复制代码


英文原文:https://github.com/lemire/simdjson


更多内容,请关注前端之巅。



2019 年 2 月 25 日 08:00 22016
用户头像

发布了 731 篇内容, 共 355.7 次阅读, 收获喜欢 1805 次。

关注

评论 3 条评论

发布
用户头像
粗略看了一下,simdjson在读取和内存分配方面做了不少的优化,
这适用于大量读取和批量转换。
跟文中作对比的几个json解析器,不在一个场景里面。

其他json解析只做串的解析,没有文件的读IO,这么比很不公平
2019 年 03 月 04 日 09:31
回复
也是觉得这个对比的角度好像很单一
如果能够从json复杂程度方面
数据量方面
实现的语言方面
多角度对比
会更有说服力
2019 年 03 月 04 日 14:44
回复
用户头像
真的能秒杀么
2019 年 02 月 27 日 20:58
回复
没有更多评论了
发现更多内容

2020年亚洲南京大数据产业展览会

南京专业智博会

展览会 论坛会 博览会 智博会

2020年南京第十三届智慧停车展会

南京专业智博会

展览会 博览会 智博会 展览会论坛会

用大数据分析了一线城市1000多份岗位招聘需求,告诉你如何科学找工作

柠檬橙

Python 数据分析

遇事不决,请先 「搜索」

Sicolas Flamel

个人成长 职场 随笔杂谈

2020亚洲南京第十三届人工智能机器人服务展览会

南京专业智博会

展览会 论坛会 博览会 智博会

10分钟白嫖我的常用的在线工具网站清单

Guide哥

设计 在线工具 工具类网站 PDF

面试造飞机系列:说下微服务接口设计原则?

柠檬橙

分布式 微服务 后端 架构设计

谈谈控制感(12):选择什么样的工作

史方远

读书笔记 个人成长 随笔杂谈

JMM的前世今生

fkc_zyk

重排序 volatile happens-before JMM cpu

2020亚洲智能家居全屋智能展会-南京站

南京专业智博会

展览会 论坛会 博览会 智博会

如何衡量产品需求效果

黄大路

产品经理 产品设计 运营

Wireshark的使用与数据分析(一)

姬翔

游戏夜读 | 记忆里的老游戏

game1night

github看代码效率提高10倍!因为用了sourcegraph这个工具

柠檬橙

GitHub 程序员 效率工具

ARTS打卡-01

Geek_yansheng25

ARTS 打卡计划

ARTS-week-1

saddamwilson

ARTS 打卡计划

带你学够浪:Go语言基础系列-环境配置和 Hello world

柠檬橙

golang 后台开发

Python 进阶与核心技术 dict & set

Bonaparte

Python 极客时间

带你学够浪:Go语言基础系列 - 8分钟学基础语法

柠檬橙

程序员 后台开发 golan

ARTS|Week 01 (2020第22周)

MiracleWong

算法 写作 ARTS 打卡计划

2020南京第十三届智慧工地装备展览会

南京专业智博会

展览会 论坛会 博览会 智博会

分布式锁🔒是个啥❓ 其实就这么点事

山中兰花草

Java redis 后端 分布式锁 开发

原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (十二)编写测试-简单测试

编程道与术

Java 编程 TDD 单元测试 JUnit

推荐几款有意思的小众App(05.30)

静陌

产品 App

Linux下程序出问题不要慌,资深程序员教你6招搞定!

柠檬橙

Linux 程序员 后台开发

这可能是 Markdown 写微信公众号的一款神器了!

JackTian

效率工具 markdown markdown编辑器 markdownnice 神器

PageHelper

BitSea

思维模型 - 概念篇

石云升

学习笔记 高效 思维模型 决策

如何优雅地实现泛型类的类型参数化

KAMI

Java 编程 反射 泛型

Jupyter最佳实践

pydata

使用 PCA 进行降维可视化,了解特征分布

黄大路

数据挖掘 数据分析 可视化

每秒解析千兆字节的JSON解析器开源,秒杀一大波解析器!-InfoQ