Tencent / rapidjson

A fast JSON parser/generator for C++ with both SAX/DOM style API
http://rapidjson.org/
Other
14.17k stars 3.53k forks source link

Document parse的性能非常慢 #772

Open qicosmos opened 7 years ago

qicosmos commented 7 years ago

测试发现调用document的parse方法解析json字符串,字符串长度为29,循环调用以千万次的时候非常耗时,并且每次parse时,内部的stack会扩容两次,每次都会resize 1024,主要性能消耗在这里,这里应该可以优化,既然用了内存池就不应该每次都扩容。希望@miloyip能优化一下parse性能,谢谢。 测试代码如下 const int LEN = 10000000; Document d; for (size_t i = 0; i < LEN; i++) { d.Parse(json); }

每调用一次Parse的时候就会Resize两次,性能全耗在这里了。

miloyip commented 7 years ago

You can:

for (size_t i = 0; i < LEN; i++)
{
   Document d;
   d.Parse(json);
}

Even better via user buffer, which is possible to make zero heap allocation:

typedef GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<>> DocumentType;
char valueBuffer[4096];
char parseBuffer[1024];

for (size_t i = 0; i < LEN; i++)
{
    MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer));
    MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer));
    DocumentType d(&valueAllocator, sizeof(parseBuffer), &parseAllocator);
    d.Parse(json);
}
qicosmos commented 7 years ago

@miloyip hi, 按照你的代码,性能反而更慢了,比之前的还慢得多,下面是测试代码: 之前的测试代码

    const int LEN = 10000000;
char * json = "{ \"Name\" : \"Boo\", \"Age\" : 28}";
Document d;
boost::timer t;
for (size_t i = 0; i < LEN; i++)
{
    d.Parse(json);
}
std::cout << t.elapsed() << std::endl;

在我的台式机上耗时11.235s 用你建议的代码

    const int LEN = 10000000;
char * json = "{ \"Name\" : \"Boo\", \"Age\" : 28}";
typedef GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<>> DocumentType;
char valueBuffer[4096];
char parseBuffer[1024];
boost::timer t;
for (size_t i = 0; i < LEN; i++)
{
    MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer));
    MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer));
    DocumentType d(&valueAllocator, sizeof(parseBuffer), &parseAllocator);
    d.Parse(json);
}

std::cout << t.elapsed() << std::endl;

耗时:59.32s

另外按理说document可以像内存池一样设置一个初始大小,比如1024,如果json串长度小于这个,就一直复用,效率应该很高才对,挺疑惑的,不知道为什么parse这里非常慢。

qicosmos commented 7 years ago

@miloyip 补充一下,如果把你建议的代码document拿出来放到for循环外面,运行会报错,应该是需要重置一下什么东西才行,但不知道调用哪个接口。document拿出来可以避免频繁的构造和析构,性能会更好。

miloyip commented 7 years ago

第一种最浪费内存;第二种每次要 malloc() 两块内存,所以较慢;第三种肯定是最省内存也最快的。

#include "rapidjson/document.h"

using namespace rapidjson;

int main() {
    const int LEN = 10000000;
    const char * json = "{ \"Name\" : \"Boo\", \"Age\" : 28}";
#if TEST == 1
    Document d;
    for (size_t i = 0; i < LEN; i++)
    {
        d.Parse(json);
    }   
#elif TEST == 2
    for (size_t i = 0; i < LEN; i++)
    {
        Document d;
        d.Parse(json);
    }
#else
    typedef GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > DocumentType;
    char valueBuffer[4096];
    char parseBuffer[1024];
    for (size_t i = 0; i < LEN; i++)
    {
        MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer));
        MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer));
        DocumentType d(&valueAllocator, sizeof(parseBuffer), &parseAllocator);
        d.Parse(json);
    }
#endif
}
Milos-MacBook-Pro:tmp miloyip$ g++ -O3 -I ~/github/rapidjson/include -DTEST=1 p.cpp && time ./a.out

real    0m3.442s
user    0m3.157s
sys 0m0.270s
Milos-MacBook-Pro:tmp miloyip$ g++ -O3 -I ~/github/rapidjson/include -DTEST=2 p.cpp && time ./a.out

real    0m8.410s
user    0m8.390s
sys 0m0.012s
Milos-MacBook-Pro:tmp miloyip$ g++ -O3 -I ~/github/rapidjson/include -DTEST=3 p.cpp && time ./a.out

real    0m2.914s
user    0m2.905s
sys 0m0.005s
qicosmos commented 7 years ago

@miloyip hi,也许你的电脑配置比较好所以时间显得比较短,我用我的电脑测试另外一个Json序列化库ajson https://github.com/lordoffox/ajson 相同条件下,另外一个要比rapidjson快得多。

include "ajson.hpp"

struct Person { std::string Name; int Age;

};

AJSON(Person, v.Name, v.Age);

int main() { Person p; const int LEN = 10000000; char * json = "{ \"Name\" : \"Boo\", \"Age\" : 28}"; boost::timer t; for (size_t i = 0; i < LEN; i++) { ajson::load_from_buff(p, json); } std::cout << t.elapsed() << std::endl; } 这个在我的电脑上耗时2.35s,而rapidjson::document耗时11s以上,后面那个改进的却要59s。ajson还包括了反序列化的过程,都比仅仅parse的也快很多。你可以在你的电脑上也对比测试一下ajson吗,也是Header-only的,希望能找到parse慢的原因,谢谢!

miloyip commented 7 years ago

什么编译器和编译参数?

qicosmos commented 7 years ago

win7 vs2015下测试的,x86 release,设置都是默认的,没有特殊优化设置。

miloyip commented 7 years ago

RapidJSON 和 AJSON 不能直接对比,两者的功能不一样。RapidJSON 的 DOM 是动态的,接受任意结构的 JSON。但也可以考虑写一个类似 AJSON 的 binding,建于 RapidJSON SAX 之上。

qicosmos commented 7 years ago

好,大概知道原因了,希望rapidjson的parse能更高效,我的一个库底层就是用的rapidjson,所以希望它的性能更高一点,谢谢你的回复!

miloyip commented 7 years ago

这个有点奇怪,我晚点用 Windows 测一下。

qicosmos commented 7 years ago

@miloyip 期待你的实验结果,thx.

guoxiao commented 7 years ago

补充一个 Linux 下面的测试结果吧,用 g++ 编译三个时间分别是:2.56s 5.26s 2.97s,用 clang++ 编译三个的时间分别是:3.35s 5.94s 3.66s 优化参数是 -O3,gcc版本 6.2.1,clang版本是3.8.0 看起来还是第一种写法最快。

qicosmos commented 7 years ago

@guoxiao 那看来和我之前的测试差不多的,我机子上第一个是最快的。

miloyip commented 7 years ago

@qicosmos 但数量级都是接近的。你测第三个 59s 好像是有点问题(可能是 buffer 对齐问题?我查一下)。