热门标签 | 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的选择


推荐阅读
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • Java多线程实现:从1到100分段求和并汇总结果
    本文介绍如何使用Java编写一个程序,通过10个线程分别计算不同区间的和,并最终汇总所有线程的结果。每个线程负责计算一段连续的整数之和,最后将所有线程的结果相加。 ... [详细]
  • 本文介绍 Java 中如何使用 Year 类的 atMonth 方法将年份和月份组合成 YearMonth 对象,并提供代码示例。 ... [详细]
  • 本文详细介绍了一种通过MySQL弱口令漏洞在Windows操作系统上获取SYSTEM权限的方法。该方法涉及使用自定义UDF DLL文件来执行任意命令,从而实现对远程服务器的完全控制。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • 深入解析ArrayList与LinkedList的差异
    本文详细对比了Java中ArrayList和LinkedList两种常用集合类的特性、性能及适用场景,通过代码示例进行测试,并结合实际应用场景分析其优缺点。 ... [详细]
  • 本文深入探讨了 Java 中 LocalTime 类的 isSupported() 方法,包括其功能、语法和使用示例。通过具体的代码片段,帮助读者理解如何检查特定的时间字段或单位是否被 LocalTime 类支持。 ... [详细]
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • 深入解析Java多线程与并发库的应用:空中网实习生面试题详解
    本文详细探讨了Java多线程与并发库的高级应用,结合空中网在挑选实习生时的面试题目,深入分析了相关技术要点和实现细节。文章通过具体的代码示例展示了如何使用Semaphore和SynchronousQueue来管理线程同步和任务调度。 ... [详细]
  • 主调|大侠_重温C++ ... [详细]
  • 在PHP后端开发中遇到一个难题:通过第三方类文件发送短信功能返回的JSON字符串无法解析。本文将探讨可能的原因并提供解决方案。 ... [详细]
  • 版本控制工具——Git常用操作(下)
    本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复 ... [详细]
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社区 版权所有