热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

优化C++项目中的JSON处理:选择高性能的RapidJSON库

在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。

在一个需要高并发处理的C++项目中,我们最初使用了JsonCpp来进行JSON数据的解析和序列化。然而,在处理较大规模的JSON数据时,JsonCpp频繁抛出异常,特别是在多线程环境中,异常发生的概率更高。

通过深入研究,我们发现这是由于使用的JsonCpp版本较老,且该版本不支持多线程安全(参考:相关讨论)。更新到最新版本后,虽然解决了部分问题,但性能仍未达到预期。通过热点分析工具,我们发现大部分时间消耗在JsonCpp的readValue函数上,尤其在处理大量浮点数值时表现不佳。

为了解决这些问题,我们引入了RapidJSON库。根据基准测试结果,RapidJSON在多线程环境下的性能表现优异。实际测试表明,在40线程的压力测试下,RapidJSON相比JsonCpp性能提升了约10倍,特别是在处理包含大量浮点数值的JSON数据时,性能提升尤为明显。

以下是我们在项目中使用RapidJSON的一些关键方法:

  1. 将通用解析方法封装在rapidjson_utils.hpp文件中:
#ifndef RAPIDJSON_UTILS_H__
#define RAPIDJSON_UTILS_H__
#include
#include
#include
#include
#include
#include

namespace utils {
using std::string;

// 获取字符串值
static int get_rapidjson_string(const rapidjson::Value& js_in, const string& key, string& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsString()) return -1;
value = pValue->GetString();
return 0;
}

// 获取整数值
static int get_rapidjson_int(const rapidjson::Value& js_in, const string& key, int& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsNumber()) return -1;
value = pValue->GetInt();
return 0;
}

// 获取浮点数值
static int get_rapidjson_number(const rapidjson::Value& js_in, const string& key, float& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsNumber()) return -1;
value = pValue->GetDouble();
return 0;
}

// 获取布尔值
static int get_rapidjson_bool(const rapidjson::Value& js_in, const string& key, bool& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsBool()) return -1;
value = pValue->GetBool();
return 0;
}

// 添加字符串成员
static void add_member_string(rapidjson::Value& node, const string& key, const string& value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value.c_str(), value.length(), alloc).Move(), alloc);
}

// 添加整数成员
static void add_member_int(rapidjson::Value& node, const string& key, int value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}

// 添加浮点数成员
static void add_member_float(rapidjson::Value& node, const string& key, double value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}

// 添加布尔成员
static void add_member_bool(rapidjson::Value& node, const string& key, bool value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}

// 将Value转换为字符串
static string to_string(const rapidjson::Value& v) {
rapidjson::StringBuffer sb;
rapidjson::Writer writer(sb);
v.Accept(writer);
return sb.GetString();
}

// 将Value转换为格式化的字符串
static string to_styled_string(const rapidjson::Value& v) {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter writer(sb);
v.Accept(writer);
return sb.GetString();
}
}
#endif // RAPIDJSON_UTILS_H__
  1. 测试代码放在rapidjson_example.cpp中:
#include 
#include
#include
#include
#include "rapidjson_utils.hpp"

using std::string;

int main(int argc, char** argv) {
string data = "{\"user\":\"hello\",\"movies\":[{\"name\":\"TENET\",\"like\":10000}],\"song\":{\"暗里着迷\":\"已经播放了3遍\"},\"ratio\":0.98}";
rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse(data.c_str());
if (!ok) {
printf("parse json failure id=%d offset=%d msg=%s\n", doc.GetParseError(), doc.GetErrorOffset(), rapidjson::GetParseError_En(doc.GetParseError()));
return -1;
}

string user;
utils::get_rapidjson_string(doc, "/user", user);
printf("user=%s\n", user.c_str());

// 遍历数组
const rapidjson::Value* pMovies = rapidjson::GetValueByPointer(doc, "/movies");
if (pMovies && pMovies->IsArray() && !pMovies->Empty()) {
for (rapidjson::Value::ConstValueIterator iter = pMovies->Begin(); iter != pMovies->End(); ++iter) {
string name;
int like;
utils::get_rapidjson_string(*iter, "/name", name);
utils::get_rapidjson_int(*iter, "/like", like);
printf("movie=%s like=%d\n", name.c_str(), like);
}
}

// 遍历对象
const rapidjson::Value* pSOng= rapidjson::GetValueByPointer(doc, "/song");
if (pSong && pSong->IsObject() && !pSong->ObjectEmpty()) {
const rapidjson::Value& sOng= *pSong;
for (rapidjson::Value::ConstMemberIterator miter = song.MemberBegin(); miter != song.MemberEnd(); ++miter) {
printf("sOng=%s message=%s\n", miter->name.GetString(), miter->value.GetString());
}
}

float ratio;
utils::get_rapidjson_number(doc, "/ratio", ratio);
printf("ratio=%f\n", ratio);

// 生成新的JSON
rapidjson::Document out(rapidjson::kObjectType);
rapidjson::Document::AllocatorType& a = out.GetAllocator();
out.CopyFrom(doc, a);
rapidjson::Value* pNewMovies = rapidjson::GetValueByPointer(out, "/movies");
rapidjson::Value movie(rapidjson::kObjectType);
utils::add_member_string(movie, "name", "Memento", a);
utils::add_member_int(movie, "like", 12366, a);
utils::add_member_float(movie, "score", 0.95, a);
pNewMovies->PushBack(movie.Move(), a);

// 新增节点
rapidjson::SetValueByPointer(out, "/version", 2.0, a);
string s = utils::to_styled_string(out);
printf("after modify json=%s\n", s.c_str());

return 0;
}

参考资料:

RapidJSON官网

Git仓库

一些JSON解析器的性能对比

关于JsonCpp和RapidJSON的选择


推荐阅读
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • Scala 实现 UTF-8 编码属性文件读取与克隆
    本文介绍如何使用 Scala 以 UTF-8 编码方式读取属性文件,并实现属性文件的克隆功能。通过这种方式,可以确保配置文件在多线程环境下的一致性和高效性。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 微软Exchange服务器遭遇2022年版“千年虫”漏洞
    微软Exchange服务器在新年伊始遭遇了一个类似于‘千年虫’的日期处理漏洞,导致邮件传输受阻。该问题主要影响配置了FIP-FS恶意软件引擎的Exchange 2016和2019版本。 ... [详细]
  • 本文探讨了在支付项目开发中使用SS5 Socket Server实现内部网络访问外部网络的技术方案。详细介绍了SS5的安装、配置及性能测试过程,旨在为面临相同需求的技术人员提供参考。 ... [详细]
  • 探讨如何通过编程技术实现100个并发连接,解决线程创建顺序问题,并提供高效的并发测试方案。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文详细介绍了如何解决Uploadify插件在Internet Explorer(IE)9和10版本中遇到的点击失效及JQuery运行时错误问题。通过修改相关JavaScript代码,确保上传功能在不同浏览器环境中的一致性和稳定性。 ... [详细]
  • 本文探讨了服务器系统架构的性能评估方法,包括性能评估的目的、步骤以及如何选择合适的度量标准。文章还介绍了几种常用的基准测试程序及其应用,并详细说明了Web服务器性能评估的关键指标与测试方法。 ... [详细]
  • 本文探讨了在Windows系统中运行Apache服务器时频繁出现崩溃的问题,并提供了多种可能的解决方案和建议。错误日志显示多个子进程因达到最大请求限制而退出。 ... [详细]
author-avatar
小心大巧
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有