別再推薦使用jsoncpp了!
事件起因
今天刷視頻看到一位up寫的json解析庫的性能超群,秒殺了一眾json解析庫,其中包括我比較喜歡的?nlohmann json
?庫,這個庫在他的測試中性能墊底,我曾寫過一個json解析庫,性能的對比對象就是這個庫,我記得是略有不如。所以我第一時間想到的是不是測試有問題,nlohmann json
?不可能這么慢啊,明明用到了大量模板的魔法??,然后我就親自把?nlohmann json
?和?jsoncpp
?以及他的庫都測了一遍。

為什么不推薦jsoncpp
如果有用過?jsoncpp
?的,以及其他語言的json庫的,我覺得都會認為?jsoncpp
?的api也太難用了吧。。。
但是如果這一切都是為了性能,那么還是有選擇余地的,但如果?jsoncpp
?的復雜并沒有帶來實質(zhì)性的性能提升,那么我們?yōu)槭裁床挥酶唵魏糜眯阅芤哺训?nlohmann json
?呢?
以下為測試源碼:Timer類用于計時
#include "time.hpp"
#include <iostream>
#include <filesystem>
#include <fstream>
using namespace std;
#include "nlohmann/json.hpp"
using json_nl = nlohmann::json;
#include"json/json.h"
using Value = Json::Value;
using Reader = Json::Reader;
#include "Json.h"
#include "my-json/Parser.h"
using my_parser = json::Parser;
//用于調(diào)整測試和驗證文件的路徑
#define BASE_DIR ?"../../"
//輸出到valid.json文件方便查看驗證是否解析正確
void outPutValidJson(std::string const& src){
?auto ofs = ofstream(BASE_DIR"valid.json");
?ofs<<src;
}
//獲取用于測試的json數(shù)據(jù)
std::string getSourceString(){
?auto ifs = ifstream(BASE_DIR"test.json");
?if(ifs){
? ?return string{istreambuf_iterator<char>(ifs),istreambuf_iterator<char>()};
?}
?return {};
}
//測試yaziJson的表現(xiàn)
void testYaziJson()
{
?//get src string
?auto data = getSourceString();
?yazi::json::Json json;
?//start bench
?{
? ?Timer t;
? ?json.parse(data);
?}
?//valid string
?outPutValidJson(json.str());
}
//測試nlohmannJson的表現(xiàn)
void testNlohmannJson()
{
? //get src string
?auto data = getSourceString();
?json_nl j;
?//start bench
?{
? ?Timer t;
? ?j = std::move(json_nl::parse(data));
?}
?//valid string
?outPutValidJson(j.dump());
}
//測試jsoncpp的表現(xiàn)
void testJsonCpp(){
?//get src string
?auto data = getSourceString();
?Value j;
?Reader r;
?//start bench
?{
? ?Timer t;
? ?if(!r.parse(data,j)){
? ? ?std::cerr<<"jsoncpp 出錯";
? ? ?return;
? ?}
?}
?//valid string
?outPutValidJson(j.toStyledString());
}
//測試我的json解析庫的表現(xiàn)
void testMyJson(){
? ?//get src string
?auto data = getSourceString();
?json::JObject j;
?//start bench
?{
? ?Timer t;
? ?j = std::move(my_parser::FromString(data));
?}
?//valid string
?outPutValidJson(j.to_string());
}
int main()
{
?for(int i=0;i<10;i++){
? ?testYaziJson();
? ?testMyJson();
? ?testJsonCpp();
? ?testNlohmannJson();
? ?std::cout<<"-----------------------"<<std::endl;
?}
}
代碼倉庫:https://github.com/ACking-you/bench_json4cpp
平均10次結(jié)果:
yazi-json: 482 us (0.482000 ms)?
my-json: 518 us (0.518000 ms)?
json-cpp: 1046 us (1.046000 ms)?
nlohmann-json: 884 us (0.884000 ms)
up寫的那個庫確實是最快的,但是最慢的不是?nlohmann json
, 而是?jsoncpp
。而且差距也沒有up視頻里面那么大,兩倍的差距而已,這對于一個淺拷貝和深拷貝的性能差距是很正常的。我估計他那里測試的時候沒開 release 優(yōu)化(對應 g++ -o3 優(yōu)化,看了源碼,果然大佬是直接寫 makefile 里面的 flag 用的 -o2 優(yōu)化),那么他視頻里的性能差距就很正常了,因為在沒開 release 優(yōu)化的前提下,某個庫如果大量用到 STL 以及元模板編程的時候,STL 會拖后腿,而元模板也沒法發(fā)揮最大效用。所以最終用于生產(chǎn)開 release 是很有必要的。
舉個簡單的例子:在大家的印象中 make_unique 或 make_shared 創(chuàng)建智能指針會優(yōu)化減少一次拷貝,那么相比直接 new 對象性能肯定會更好,你可以試試,實際測試中如果是 debug 情況下,直接 new 大概率會更快,但是切到 release 模式兩者效率都有進步,但 make 系列函數(shù)調(diào)用的優(yōu)化會更多。

json解析如何優(yōu)化性能
C++ json解析的性能瓶頸就在于?std::string
?等STL容器默認的拷貝行為,一個簡單粗暴的優(yōu)化方式就是通過傳指針的方式而不是傳值的方式,但是這樣外界就需要來管理內(nèi)存了,否則會產(chǎn)生內(nèi)存泄漏等問題。前面那個大佬寫的庫處理方式就是這樣的。
熟悉cpp的朋友,大概率想到避免拷貝的方式就是移動,只要強制對象只能移動不能拷貝就行了。
洞察我之前寫的json解析庫,發(fā)現(xiàn)還有一個地方經(jīng)常需要拷貝,而且還沒法避免。string 類型的substr方法,所以我換成了string_view。這樣在內(nèi)部使用的內(nèi)存和外界就一致了,這就需要用戶確保這個內(nèi)存不會被修改或回收。傳入字符串進行解析很明顯不應該擁有所有權,即便擁有也應該把外界的內(nèi)存移動進來,所以采取string_view則是不擁有所有權的方式優(yōu)化。同時還有個好處就是兼容了C-style字符串。
具體實現(xiàn)代碼:https://github.com/ACking-you/bench_json4cpp/tree/master/my-json