赞
踩
最近在我的写了一个API程序,要求返回响应的数据是JSON,格式类似下面这种:
[{
"columns":[
{"text":"Time","type":"time"},
{"text":"Country","type":"string"},
{"text":"Number","type":"number"}
],
"rows":[
[1234567,"SE",123],
[1234567,"DE",231],
[1234567,"US",321]
],
"type":"table"
}]
API是用C语言开发的,所以在构建json上,为了方便,使用了CJson库。代码如下:
static char *grafana_build_reponse_query_history(MYSQL *mysql, const char *request_body, grafana_query_request_s *rst, snetflow_job_s *job) { cJSON *root, *response_json, *columns, *column, *rows, *row, *json_flow, *json_bytes; ... /* 根据target拼接json */ response_json = cJSON_CreateArray(); root = cJSON_CreateObject(); cJSON_AddItemToArray(response_json, root); rows = cJSON_AddArrayToObject(root, "rows"); columns = cJSON_AddArrayToObject(root, "columns"); cJSON_AddStringToObject(root, "type", "table"); /* rows */ for(i = 0, it = history_vec.begin(); it != history_vec.end(); it++) { json_flow = cJSON_CreateString((*it).flow); json_bytes = cJSON_CreateNumber((*it).bytes); row = cJSON_CreateArray(); cJSON_AddItemToArray(row, json_flow); cJSON_AddItemToArray(row, json_bytes); /* 优化cJSON_AddItemToArray(rows, row)提高速度*/ cJSON_AddItemToArray(rows, row); } /* columns */ column = cJSON_CreateObject(); cJSON_AddStringToObject(column, "text", cfg->name); cJSON_AddStringToObject(column, "type", "string"); cJSON_AddItemToArray(columns, column); column = cJSON_CreateObject(); cJSON_AddStringToObject(column, "text", "bytes"); cJSON_AddStringToObject(column, "type", "number"); cJSON_AddItemToArray(columns, column); /* 释放空间 */ out = cJSON_Print(response_json); cJSON_Delete(response_json); return out; }
对于row,思路就是遍历一个map,然后组装一个数组,然后添加到大数组中。当map容量在几千的时候,还可有跑出结果,但是当map容量大到几十万的时候,跑了一个多小时还没有结果,而且几乎耗尽了CPU。我用gdb调试了一下,发现大部分时间是浪费在cJSON_AddItemToArray这个函数。
我们可以看一下cJSON结构体的定义:
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
int type;
char *valuestring;
int valueint;
double valuedouble;
char *string;
} cJSON;
这里面有三个指针。以这个json格式举例:
[{
"columns":[
{"text":"Time","type":"time"},
{"text":"Country","type":"string"},
{"text":"Number","type":"number"}
],
"rows":[
[1234567,"SE",123],
[1234567,"DE",231],
[1234567,"US",321]
],
"type":"table"
}]
其中columns、rows、type称作同级,{“text”:“Time”,“type”:“time”},是columns的一个子级,[1234567,“DE”,231],是rows的一个子级。
在这个结构体中,next指向同级的下一个,prev指向同级的上一个,也就是说,同级之前构成了一个双向链表。child指向子级的第一个节点。
那么问题来了,cJSON_AddItemToArray内部是如何实现的呢?很显然,无非就是修改这几个指针的指向,虽然这是一个双向链表,但是却没有记录头和尾。所以,每次调用cJSON_AddItemToArray的时候,只能从头部向后遍历,使用尾插法,时间复杂度是n^2。当数据量很大的时候,有多么耗时就可想而知了。
(如果用头插法,会导致json串同级数据的顺序颠倒!所以,cJSON_AddItemToArray肯定采用的尾插法)
既然是我们已经了解了cJSON_AddItemToArray实现思路,所以我们就可以使用单链表的尾插法,来自己对这个函数进行改进,把时间复杂度降到n。
可以参考一下如下代码
for(i = 0, it = history_vec.begin(); it != history_vec.end(); it++) { json_flow = cJSON_CreateString((*it).flow); json_bytes = cJSON_CreateNumber((*it).bytes); row = cJSON_CreateArray(); cJSON_AddItemToArray(row, json_flow); cJSON_AddItemToArray(row, json_bytes); /* 优化cJSON_AddItemToArray(rows, row)提高速度*/ if(i == 0) { i = 1; cJSON_AddItemToArray(rows, row); prev = row; } else { row->prev = prev; prev->next = row; prev = row; } }
同理,其他的一些Add函数,如cJSON_AddItemToObject、cJSON_AddItemReferenceToObject等也可以使用这个思路优化。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。