Simdjson:一個(gè)超高速的JSON解析工具
JSON文檔在Internet上無(wú)處不在,服務(wù)器花費(fèi)大量時(shí)間來(lái)解析這些文檔。我們希望在進(jìn)行完全驗(yàn)證(包括字符編碼)時(shí)盡可能使用常用的SIMD指令來(lái)加速JSON本身的解析。
表現(xiàn)結(jié)果
simdjson使用的指令比解析器RapidJSON少四分之三,比sajson少百分之五十。據(jù)我們所知,simdjson是一個(gè)在商用處理器上以每秒千兆字節(jié)運(yùn)行的完全驗(yàn)證的JSON解析器。
在Skylake處理器上,twitter.json文件上各種處理器的解析速度(以GB / s為單位)如下。
| 解析器 | GB /秒 | 
|---|---|
| simdjson | 2.2 | 
| RapidJSON編碼驗(yàn)證 | 0.51 | 
| RapidJSON編碼驗(yàn)證,原位 | 0.71 | 
| sajson(原狀,動(dòng)態(tài)) | 0.70 | 
| sajson(insitu,static) | 0.97 | 
| dropbox | 0.14 | 
| FASTJSON | 0.26 | 
| gason | 0.85 | 
| ultrajson | 0.42 | 
| jsmn | 0.28 | 
| cJSON | 0.34 | 
要求
我們通過(guò)Visual Studio 2017或更高版本支持Linux或macOS等平臺(tái)以及Windows;
帶有高級(jí)矢量擴(kuò)展指令集的處理器(即,2013年發(fā)布的Haswell微體系結(jié)構(gòu)的Intel處理器和2017年發(fā)布的Zen微體系結(jié)構(gòu)的AMD處理器);
最近的C ++編譯器(例如,GNU GCC或LLVM CLANG或Visual Studio 2017),我們假設(shè)C ++ 17。GNU GCC 7或更高版本或LLVM的clang 6或更高版本。
License
此代碼在Apache License 2.0下提供。
在Windows下,我們使用 windows/dirent_portable.h 文件(在我們的庫(kù)代碼之外)構(gòu)建一些工具
代碼示例
- #include "simdjson/jsonparser.h"
 - /...
 - const char * filename = ... //
 - //使用您想要的任何方式獲取JSON文檔的字符串
 - std::string_view p = get_corpus(filename);
 - ParsedJson pj;
 - pj.allocateCapacity(p.size());//分配內(nèi)存以解析p.size()字節(jié)
 - const int res = json_parse(p, pj); //進(jìn)行解析,成功時(shí)返回0
 - //解析完成!
 - if(res!= 0){
 - //您可以使用“simdjson / simdjson.h”標(biāo)頭來(lái)訪問(wèn)錯(cuò)誤消息
 - std::cout << "Error parsing:" << simdjson::errorMsg(res) << std::endl;
 - }
 - //你可以安全地刪除字符串內(nèi)容
 - free((void*)p.data());
 - //可以在這里使用ParsedJson文檔
 - // js可以與其他json_parse調(diào)用一起使用。
 
如果您不介意為每個(gè)新的JSON文檔分配內(nèi)存開(kāi)銷,也可以使用更簡(jiǎn)單的API:
- #include "simdjson/jsonparser.h"
 - / ...
 - const char * filename = ... //
 - std::string_view p = get_corpus(filename);
 - ParsedJson pj = build_parsed_json(p); //進(jìn)行解析
 - //此時(shí)你不再需要p,可以執(zhí)行aligned_free((void *)p.data())
 - if( ! pj.isValid() ) {
 - //出錯(cuò)了
 - }
 
用法:簡(jiǎn)單的版本
有關(guān)用法,請(qǐng)參閱“singleheader”存儲(chǔ)庫(kù)的文件“amalgamation_demo.cpp”。這不需要特定的構(gòu)建系統(tǒng):只需在包含路徑中復(fù)制項(xiàng)目中的文件即可。然后,您可以非常簡(jiǎn)單地包含它們:
- #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;
 - }
 
我們需高級(jí)矢量擴(kuò)展指令集指令的硬件支持。您必須確保指示編譯器根據(jù)需要使用這些說(shuō)明。在GNU GCC或LLVM clang等編譯器下, -march=native 最近的Intel處理器(Haswell或更好)上使用的標(biāo)志就足夠了。為了便于二進(jìn)制文件的可移植性,您還可以直接指定Haswell處理器( -march=haswell )。
注意:在某些設(shè)置中,可能需要預(yù)編譯 simdjson.cpp 而不是包含它。
用法(在Linux或macOS等平臺(tái)上使用舊版Makefile)
要求:最近的clang或gcc,和make。我們建議至少使用GNU GCC / G ++ 7或LLVM clang 6.需要像Linux或macOS這樣的系統(tǒng)。
測(cè)試:
- make
 - make test
 
要運(yùn)行基準(zhǔn)測(cè)試:
- make parse
 - ./parse jsonexamples/twitter.json
 
在Linux下,該 parse 命令提供了性能計(jì)數(shù)器的詳細(xì)分析。
運(yùn)行比較基準(zhǔn)測(cè)試(與其他解析器):
- make benchmark
 
用法(在Linux或macOS等平臺(tái)上使用CMake)
要求:我們需要新版本的cmake。在macOS上,安裝cmake的最簡(jiǎn)單方法可能是使用 brew然后鍵入
- brew install cmake
 
在Linux上 有一個(gè) 相同的Brew也可以以相同的方式工作 。
你需要一個(gè)像clang或gcc這樣的新編譯器。我們建議至少使用GNU GCC / G ++ 7或LLVM clang 6.例如,您可以使用brew安裝新的編譯器:
- brew install gcc@8
 
可選:您需要通過(guò)設(shè)置CC和CXX變量告訴cmake您希望使用哪個(gè)編譯器。bash下,你可以用諸如命令這樣做 export CC=gcc-7 和 export CXX=g++-7 。
構(gòu)建:在項(xiàng)目存儲(chǔ)庫(kù)中,執(zhí)行以下操作:
- mkdir build
 - cd build
 - cmake ..
 - make
 - make test
 
默認(rèn)情況下,它構(gòu)建一個(gè)共享庫(kù)(例如,Linux上的libsimdjson.so)。
您可以構(gòu)建一個(gè)靜態(tài)庫(kù):
- mkdir buildstatic
 - cd buildstatic
 - cmake -DSIMDJSON_BUILD_STATIC=ON ..
 - make
 - make test
 
在某些情況下,您可能希望指定編譯器,尤其是在系統(tǒng)上的默認(rèn)編譯器太舊的情況下。您可以按以下步驟操作:
- brew install gcc@8
 - mkdir build
 - cd build
 - export CXX=g++-8 CC=gcc-8
 - cmake ..
 - make
 - make test
 
用法(使用Visual Studio在Windows上進(jìn)行CMake)
我們假設(shè)您有一臺(tái)普通的Windows PC,至少包含Visual Studio 2017和支持高級(jí)矢量擴(kuò)展指令集的x64處理器(2013 Intel Haswell或更高版本)。
從GitHub獲取simdjson代碼,例如,使用 GitHub Desktop 克隆它;
安裝 CMake 。安裝時(shí),請(qǐng)確保 cmake 從命令行詢問(wèn)是否可用。請(qǐng)選擇新版本的cmake;
在simdjson中創(chuàng)建一個(gè)子目錄,例如 VisualStudio;
使用shell,轉(zhuǎn)到這個(gè)新創(chuàng)建的目錄;
cmake -DCMAKE_GENERATOR_PLATFORM=x64 .. 在 VisualStudio 存儲(chǔ)庫(kù)中鍵入shell 。(或者,如果要構(gòu)建DLL,可以使用命令行 cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DSIMDJSON_BUILD_STATIC=OFF .. )
末尾一個(gè)命令在新創(chuàng)建的目錄中創(chuàng)建了一個(gè)Visual Studio解決方案文件(例如 simdjson.sln)。在Visual Studio中打開(kāi)此文件。您現(xiàn)在應(yīng)該能夠構(gòu)建項(xiàng)目并運(yùn)行測(cè)試。例如,在 Solution Explorer 窗口(可從 View 菜單中獲得)中,右鍵單擊 ALL_BUILD 并選擇 Build 。要測(cè)試代碼,仍然在 Solution Explorer 窗口中,選擇 RUN_TESTS 并選擇 Build 。
用法(在Windows,Linux和MacOS上使用vcpkg)
Windows,Linux和MacOS上的 vcpkg 用戶可以 simdjson 使用他們喜歡的shell中的一個(gè)命令下載和安裝。
在Linux和MacOS上:
- $ ./vcpkg install simdjson
 
將構(gòu)建并安裝 simdjson 為靜態(tài)庫(kù)。
在Windows(64位)上:
- .\vcpkg.exe install simdjson:x64-windows
 
將構(gòu)建并安裝 simdjson 為共享庫(kù)。
- .\vcpkg.exe install simdjson:x64-windows-static
 
將構(gòu)建并安裝 simdjson 為靜態(tài)庫(kù)。
這些命令還將打印出有關(guān)如何使用MSBuild或基于CMake的項(xiàng)目庫(kù)的說(shuō)明。
如果您發(fā)現(xiàn) simdjson 附帶的版本 vcpkg 已過(guò)期,請(qǐng)隨時(shí)通過(guò)提交 vcpkg 問(wèn)題或創(chuàng)建PR 向社區(qū)報(bào)告。
工具
json2json mydoc.json 解析文檔,構(gòu)造模型,然后將結(jié)果轉(zhuǎn)儲(chǔ)回標(biāo)準(zhǔn)輸出
json2json -d mydoc.json 解析文檔,構(gòu)造模型,然后將模型(作為磁帶)轉(zhuǎn)儲(chǔ)到標(biāo)準(zhǔn)輸出。磁帶格式在隨附文件中描述 tape.md
minify mydoc.json`縮小JSON文檔,將結(jié)果輸出到標(biāo)準(zhǔn)輸出??s小意味著刪除不需要的空格字符。
范圍
我們提供快速解析器。它根據(jù)各種規(guī)格完全驗(yàn)證輸入。解析器構(gòu)建一個(gè)有用的不可變(只讀)DOM(文檔 – 對(duì)象模型),以后可以訪問(wèn)它。
為了簡(jiǎn)化工程,我們做了一些假設(shè):
我們支持UTF-8(以及ASCII),沒(méi)有別的(沒(méi)有拉丁語(yǔ),沒(méi)有UTF-16)。我們不認(rèn)為這是一個(gè)真正的限制,因?yàn)槲覀冋J(rèn)為沒(méi)有任何嚴(yán)重的應(yīng)用程序需要在沒(méi)有ASCII或UTF-8編碼的情況下處理JSON數(shù)據(jù);
JSON文檔中的所有字符串最多可包含UTF-8(4GB)中的4294967295個(gè)字節(jié)。要強(qiáng)制執(zhí)行此約束,我們拒絕解析包含超過(guò)4294967295字節(jié)(4GB)的文檔。這應(yīng)該適應(yīng)大多數(shù)JSON文檔;
我們假設(shè)高級(jí)矢量擴(kuò)展指令集支持在AMD和英特爾生產(chǎn)的所有主流x86處理器中都可用。盡管可以完成,但不包括對(duì)非x86處理器的支持。我們計(jì)劃支持ARM處理器(請(qǐng)求幫助);
如果發(fā)生故障,我們只會(huì)報(bào)告故障,而不會(huì)指出問(wèn)題的性質(zhì)。(這可以在不影響性能的情況下輕松改進(jìn));
在規(guī)范允許的情況下,我們?cè)试S對(duì)象內(nèi)的重復(fù)鍵(像sajson這樣的其他解析器也這樣做);
性能針對(duì)跨越至少幾十千字節(jié)到幾兆字節(jié)的JSON文檔進(jìn)行了優(yōu)化:必須解析許多小型JSON文檔或一個(gè)真正龐大的JSON文檔的性能問(wèn)題是不同的。
我們的目標(biāo)不是提供通用的JSON庫(kù)。像RapidJSON這樣的庫(kù)提供的不僅僅是解析,它還可以幫助您生成JSON并提供各種其他方便的功能。我們只解析文檔。
特征
輸入字符串未修改,(像sajson和RapidJSON這樣的解析器使用輸入字符串作為緩沖區(qū))。
我們將整數(shù)和浮點(diǎn)數(shù)解析為單獨(dú)的類型,這允許我們支持[-9223372036854775808,9223372036854775808]中的大型64位整數(shù),如C / C ++ long long 。在區(qū)分整數(shù)和浮點(diǎn)數(shù)的解析器中,并非所有解析器都支持64位整數(shù)。(例如,sajson拒絕整數(shù)大于或等于2147483648的JSON文件.FreeJSON將解析包含過(guò)長(zhǎng)整數(shù)的文件,如18446744073709551616作為浮點(diǎn)數(shù))當(dāng)我們無(wú)法將整數(shù)表示為帶符號(hào)的64位時(shí)值,我們拒絕JSON文檔。
在解析過(guò)程中進(jìn)行完整的UTF-8驗(yàn)證(像fastjson,gason和dropbox json11這樣的解析器不會(huì)進(jìn)行UTF-8驗(yàn)證);完全驗(yàn)證了這些數(shù)字(像gason和ultranjson這樣的解析器將接受 [0e+] 為有效的JSON);驗(yàn)證未轉(zhuǎn)義字符的字符串內(nèi)容(像fastjson和ultrajson這樣的解析器接受未轉(zhuǎn)義的換行符和字符串中的標(biāo)簽)。
Architecture
解析器分兩個(gè)階段工作:
階段1.(查找標(biāo)記)快速標(biāo)識(shí)結(jié)構(gòu)元素,字符串等。我們?cè)谀莻€(gè)階段驗(yàn)證UTF-8編碼。
階段2.(結(jié)構(gòu)構(gòu)建)涉及構(gòu)建排序的“樹(shù)”(具體化為磁帶)以瀏覽數(shù)據(jù)。在此階段解析字符串和數(shù)字。
導(dǎo)航已解析的文檔
以下是將解析后的JSON轉(zhuǎn)儲(chǔ)回字符串的代碼示例:
- 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
 - }
 - }
 
以下函數(shù)將查找所有user.id整數(shù):
- 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;
 - }
 - }
 
深入比較
如果您想了解各種解析器如何驗(yàn)證給定的JSON文件:
- make allparserscheckfile
 - ./allparserscheckfile myfile.json
 
對(duì)于性能比較:
- make parsingcompetition
 - ./parsingcompetition myfile.json
 
進(jìn)行更廣泛的比較:
- make allparsingcompetition
 - ./allparsingcompetition myfile.json
 
















 
 
 


 
 
 
 