最近一个项目在使用JsonCpp,JsonCpp简洁易用的接口让人印象深刻。但是在实际使用过程中,我发现JsonCpp的性能却不尽如人意,所以想着方法优化下性能。
代码理解
1、JsonCpp中一切都是Value,Value用union指向自己保存的数据。Value的类型分为两种,一种是容器类型,比如arrayValue和objectValue。二者都是用map保存数据,只是arrayValue的key为数字而已。另外一种是基本类型,比如字符串,整型数字等等。
2、解释JSON数据时,JsonCpp在operator[]函数开销比较大。JsonCpp内部使用std::map,查找性能方便,map不如hash_map,但是将map替换成hash_map有一定的困难,因为map的key为CZString,而这个类又是Value的内部类,导致不能定义hash_map需要的hash结构体。
本来想尝试下internal map,结果开启JSON_VALUE_USE_INTERNAL_MAP这个宏之后,根本通不过编译,因为value.h中有一处uion声明里面居然放的是结构体,不知道什么编译器支持这种语法。
基准测试程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
#include <iostream>
#include <string>
#include <sys/time.h>
#include <time.h>
#include <json/json.h>
using
namespace
std;
int64_t getCurrentTime()
{
struct
timeval tval;
gettimeofday(&tval, NULL);
return
(tval.tv_sec * 1000000LL + tval.tv_usec);
}
char
* str =
"abcdefghijklmnopqrstuvwxyz"
;
void
test1()
{
int
doc_count = 40;
int
outer_field_count = 80;
Json::Value common_info;
int64_t start_time = getCurrentTime();
for
(
size_t
i=0; i<doc_count; ++i)
{
Json::Value auc_info;
for
(
size_t
j=0 ; j<outer_field_count; ++j )
{
auc_info.append(str);
}
common_info.append(auc_info);
}
int64_t end_time = getCurrentTime();
cout <<
"append time: "
<< end_time - start_time << endl;
}
void
test2()
{
int
doc_count = 40;
int
outer_field_count = 80;
Json::Value common_info;
int64_t start_time = getCurrentTime();
Json::Value auc_info;
for
(
size_t
i=0; i<doc_count; ++i)
{
for
(
size_t
j=0 ; j<outer_field_count; ++j )
{
auc_info[j] = str;
}
common_info.append(auc_info);
}
int64_t end_time = getCurrentTime();
cout <<
"opt append time: "
<< end_time - start_time << endl;
}
void
test3()
{
int
doc_count = 40;
int
outer_field_count = 80;
Json::Value common_info;
int64_t start_time = getCurrentTime();
Json::Value auc_info;
for
(
size_t
i=0; i<doc_count; ++i)
{
for
(
size_t
j=0 ; j<outer_field_count; ++j )
{
auc_info[j] = Json::StaticString(str);
}
common_info.append(auc_info);
}
int64_t end_time = getCurrentTime();
cout <<
"StaticString time: "
<< end_time - start_time << endl;
}
int
main(
int
argc,
const
char
*argv[])
{
test1();
test2();
test3();
return
0;
}
|
编译优化
默认情况下,JsonCpp编译时并没有带优化参数,自己可以加上优化参数。Linux环境下在下面这段代码中的CCFLAGS加入”O2″。
1
2
3
4
|
elif
platform.startswith(
'linux-gcc'
):
env.Tool(
'default'
)
env.Append( LIBS
=
[
'pthread'
], CCFLAGS
=
"-Wall -fPIC O2"
)
env[
'SHARED_LIB_ENABLED'
]
=
True
|
可以看到使用O2优化比默认编译的版本性能提升一倍多。
1
2
3
4
5
6
7
|
append time: 4946
opt append time: 3326
StaticString time: 2750
append time: 1825
opt append time: 1082
StaticString time: 845
|
使用方法上的优化
测试代码中第三种方法比第一种方法效率提升了一倍多。第三种方法之所以效率更高,有两个原因。
1、首先是在循环中一直复用auc_info对象。第一个循环就能将auc_info的长度初始化为doc_count。通过下标的访问方法,一直复用数组中的元素。
2、如果key和value内存不会被释放,那么使用StaticString效率会更高,省去了构造CZString时拷贝的开销。
代码优化
因为在JsonCpp中一切都是Value,所以会有大量的隐性类型转换,要构造大量的Value对象。为了提高性能,可以在实现绕过这个机制,牺牲一致性。
因为Value最常用的类型是字符串,因此给Value增加一个setValue函数。
1
2
3
4
5
6
7
|
void
Value::setValue(
const
StaticString& value )
{
type_ = stringValue;
allocated_ =
false
;
value_.string_ =
const_cast
<
char
*>( value.c_str() );
}
|
再测试一下性能,可以发现性能较第三种方法还有提升。
1
2
3
4
|
append time: 1849
opt append time: 1067
StaticString time: 843
setValue time: 570
|
最后还有一个办法就是静态链接。JsonCpp库本身非常小,将其静态链接能稍微提升一点性能。下面是静态链接时基准测试程序的耗时情况。
1
2
3
4
|
append time: 1682
opt append time: 1011
StaticString time: 801
setValue time: 541
|