每秒解析千兆字节的 JSON 解析器开源,秒杀一大波解析器!
近日,GitHub开源了一JSON解析器simdjson,通过与其他常用解析器的对比实验,结果显示,simdjson的解析速度达到2.2GB/s,远远秒杀其他解析器,在下文中,我们将为大家详细介绍simdjson。以下全文为simdjson在GitHub上的文档。
JSON文档在互联网上无处不在,服务器花费大量时间来解析这些文档。我们希望在进行完全验证(包括字符编码)的同时尽可能使用常用的SIMD指令来加速JSON的解析。
一些性能结果相比最先进的解析器(如RapidJSON),我们可能使用四分之一或更少的指令,也只有sajson的一半。据我们所知,simdjson是第一个在商用处理器上以每秒千兆字节速度运行的完全验证JSON解析器。
在Skylake处理器上,各种解析器解析文件的速度(以GB/s为单位)如下所示。
基本要求通过VisualStudio2017或更高版本支持Linux、macOS以及Windows等平台;
带有AVX2的处理器;
支持最近的C++编译器(例如,GNUGCC或LLVMCLANG或VisualStudio2017),我们假设是C++17,GNUGCC7或更高版本,或者LLVM的clang6或更高版本;
提供一些基准测试脚本,可以是bash和其他常用的实用命令程序,但是是可选的。
许可代码采用许可。
在Windows下,我们使用windows/dirent_文件(在我们的库代码之外)构建了一些工具:基于自由的MIT许可。
代码示例复制代码
include"simdjson/"/constchar*filename=//std::string_viewp=get_corpus(filename);ParsedJsonpj=build_parsed_json(p);//dotheparsing//younolongerneedpatthispoint,candoaligned_free((void*)())if(!()){//somethingwentwrong}用法简单的头文件
头文件可以看一下代码库的“singleheader”,用法可以看一下“amalgamation_”文件。这里不要求使用特定的构建系统:只需要将文件复制到项目中的路径中即可。然后,你就可以包含它们:
复制代码
include""#include""intmain(intargc,char*argv[]){constchar*filename=argv[1];std::string_viewp=get_corpus(filename);ParsedJsonpj=build_parsed_json(p);//dotheparsingif(!()){std::cout"notvalid"std::l;}else{std::cout"valid"std::l;}returnEXIT_SUCCESS;}注意:在某些环境中,可能需要预编译,而不是包含它。
在Linux或macOS等平台上使用旧版Makefile
要求:最近的clang(或gcc)和make。我们建议至少使用GNUGCC/G++7或LLVMclang6,Linux或macOS系统。
测试:
复制代码
makemaketest
运行基准测试:
复制代码
makeparse./parsejsonexamples/
在Linux上,parse命令提供了性能计数器的详细分析。
运行其他作为比较的基准测试(使用其他解析器):
复制代码
makebenchmark
使用Linux或macOS等平台上的CMake
要求:需要最新版本的cmake,在macOS上,安装cmake的最简单方法可能是使用brew。
复制代码
brewinstallcmake
你需要一个像clang或gcc这样的新版编译器。我们建议至少使用GNUGCC/G++7或LLVMclang6。例如,你可以使用brew安装最新的编译器:
复制代码
brewinstallgcc@8
可选:你需要通过设置CC和CXX变量告诉cmake你希望使用哪个编译器。在bash中,你可以使用exportCC=gcc-7和exportCXX=g+±7等命令。
构建:在项目代码库中执行以下命令:
复制代码
mkdirbuildcdbuildcmake..makemaketest
CMake将会构建出一个库。默认情况下,它构建的是一个共享库(例如,)。
你可以构建一个静态库:
复制代码
mkdirbuildstaticcdbuildstaticcmake-DSIMDJSON_BUILD_STATIC=ON..makemaketest
在某些情况下,你可能希望指定编译器,尤其是当系统默认编译器太旧的情况下。你可以按以下步骤操作:
复制代码
brewinstallgcc@8mkdirbuildcdbuildexportCXX=g++-8CC=gcc-8cmake..makemaketest
通过VisualStudio在Windows上使用CMake
我们假设你拥有一台至少装有VisualStudio2017的普通WindowsPC,并支持AVX2的x64处理器(2013Haswell或更高版本)。
从GitHub获取simdjson代码,例如,使用GitHubDesktop克隆它。
安装CMake。在安装时,请确保可以从命令行使用cmake。请选择最新版本的cmake。
在simdjson中创建一个子目录,例如VisualStudio。
在shell中转到这个新创建的目录。
在shell中键入cmake-DCMAKE_GENERATOR_PLATFORM=x64…(或者,如果要构建DLL,可以使用命令行cmake-DCMAKE_GENERATOR_PLATFORM=x64-DSIMDJSON_BUILD_STATIC=OFF…)。
最后一个命令在新创建的目录(例如)中创建了一个VisualStudio解决方案文件。在VisualStudio中打开这个文件。你现在应该能够构建项目并运行测试。例如,在“SolutionExplorer”窗口中,右键单击“ALL_BUILD”,并选择“Build”。要测试代码,仍然在SolutionExplorer窗口中,选择RUN_TESTS,再选择Build。
工具解析文档,构造模型,然后将结果输出到标准输出。
解析文档,构造模型,然后将模型输出到标准输出。格式在随附的文件中有描述。
缩小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++的longlong。在区分整数和浮点数的解析器中,并非所有解析器都支持64位整数。(例如,sajson不支持包含大于或等于2147483648整数的JSON文件。FreeJSON将长整数解析为浮点数。)当我们无法将整数表示为带符号的64位值时,我们就拒绝解析JSON文档。
在解析过程中进行完整的UTF-8验证。(像fastjson、gason和dropboxjson11这样的解析器不会进行UTF-8验证。)
完全验证数字。(像gason和ultranjson这样的解析器会接受[0e+]这样的数字。)
验证字符串内容中的未转义字符。(像fastjson和ultrajson这样的解析器接受字符串中未转义的换行符和制表符。)
架构解析器分三个阶段:
阶段1,(查找标记)快速标识结构元素、字符串等。我们在这个阶段验证UTF-8编码。
阶段2,(结构构建)构建排序的“树”(物化为磁带),以方便访问数据。我们在这个阶段解析字符串和数字。
访问已解析的文档以下是将解析后的JSON转储回字符串的代码示例:
复制代码
ParsedJson::iteratorpjh(pj);if(!()){std::cerr"Couldnotiterateparsedresult."std::l;returnEXIT_FAILURE;}compute_dump(pj);////wherecompute_dumpis:voidcompute_dump(ParsedJson::iteratorpjh){if(_object()){std::cout"{";if(()){(std::cout);//mustbeastringstd::cout":";();compute_dump(pjh);//letusrecursewhile(()){std::cout",";(std::cout);std::cout":";();compute_dump(pjh);//letusrecurse}();}std::cout"}";}elseif(_array()){std::cout"[";if(()){compute_dump(pjh);//letusrecursewhile(()){std::cout",";compute_dump(pjh);//letusrecurse}();}std::cout"]";}else{(std::cout);//justprintthelonevalue}}下面的函数将找出所有的整数:
复制代码
voidsimdjson_traverse(std::vectorint64_tanswer,ParsedJson::iteratori){switch(_type()){case'{':if(()){do{boolfounduser=equals(_string(),"user");();//movetovalueif(_object()){if(_to_key("id")){if(_integer()){_back(_integer());}();}simdjson_traverse(answer,i);}elseif(_array()){simdjson_traverse(answer,i);}}while(());();}break;case'[':if(()){do{if(_object_or_array()){simdjson_traverse(answer,i);}}while(());();}break;case'l':case'd':case'n':case't':case'f':default:break;}}深度比较如果你想了解各种解析器如何验证给定的JSON文件:
复制代码
makeallparserscheckfile./
性能比较:
复制代码
makeparsingcompetition./
更广泛的比较:
复制代码
makeallparsingcompetition./







