前言
最近学习了字节开源的json库sonic-cpp,号称其性能为rapidjson的2.5倍,于是决定并验证一下,并结合qt的开源库qt-material-widgets与qcustomplot可视化测试结果,主要内容包括:1,sonic-cpp与rapidjson的benchmark测试;2,通过网络执行耗时较长的任务的解决方案;3,利用qt-material-widgets与qcustomplot等开源库生成界面。工程整体与上一篇文章类似(利用c++实现基于rpc的远端服务器参数实时查询 - 掘金 (juejin.cn))。
一、基于benchmark的sonic-cpp与rapidjson的性能对比
sonic-cpp(github.com/bytedance/s... )有c++和go版本的,下载好后,里面包含sonic-cpp、rapidjson、cjson、simdjson、yyjson等多种json库的benchmark(github.com/google/benc... )测试代码,要求c++17及以上,里面用到了std::filesystem,需要添加依赖stdc++fs;sonic-cpp提供了一个set_arch_flags的cmake函数也需要用到,其它正常编译即可。这里只对比sonic-cpp和rapidjson,删除无用代码。sonic-cpp提供的数据集里有一些几十M的的json文件,电脑性比较差的话可能会直接卡死,测试的时候将这些数据集排除掉。测试结果如下,将前3列数据的解析结果通过网络发送到客户端。 从结果上看,所有的测试场景的decode和encode,基本上都是sonic-cpp快,原因可能是进行了向量化(SIMD)优化(www.infoq.cn/article/Exk... )吧,不懂。
二、rpc通信的一些问题
1、通过网络执行耗时较长的任务的解决方案
这里耗时较长是指,客户端发起测试请求,服务端开始benchmark测试,客户端一直等待服务端返回数据。看了一下,sonic-cpp与rapidjson的性能测试共耗时大概60多秒。一般是客户端先采用发起一次请求命令,服务端会返回一个任务id,然后客户端利用任务id间隔一小段时间不停的向服务端查询结果,直至成功或失败。由于服务端需要处理多个客户端请求,因此需要用任务id来区分。按照这种方法,定义pb结构如下:
ini
syntax = "proto2";
option cc_generic_services = true;
enum StatusType{
FAIL = 0;
PROGESSING = 1;
FINISH = 2;
}
message BenchmarkJsonInfo {
required string testName = 1;
required uint32 testTime = 2;
required uint32 cpuTime = 3;
};
//生成任务
message BenchmarkJsonTaskRequest {
};
message BenchmarkJsonTaskResponse {
required int32 taskId = 1;
};
service BenchmarkJsonTaskService {
rpc BenchmarkJsonTask(BenchmarkJsonTaskRequest) returns (BenchmarkJsonTaskResponse);
};
//查询状态
message QueryBenchmarkStatusRequest {
required int32 taskId = 1;
};
message QueryBenchmarkStatusResponse {
required StatusType status = 1;
required string errInfo = 2;
repeated BenchmarkJsonInfo benchmarkJsonInfos = 3;
};
service QueryBenchmarkStatusService {
rpc QueryBenchmarkStatus(QueryBenchmarkStatusRequest) returns (QueryBenchmarkStatusResponse);
};
2、基于brpc的较大数据传输的数据帧拼接问题
这里服务端采用brpc实现,客户端是Windows无法使用brpc,因而采用libevent实现rpc客户端。测试数据大概有3000个字节,发现libevent分两次进入了读回调函数,因此需要拼接数据。按照brpc协议进行如下简单的拼接数据,第一次收到数据时,解析第4~8个字节获得总数据大小(注意&的优先级低于<<,需要加括号),第二次收到数据时利用总字节数进行校验。
arduino
TcpClientCallBack::JoinDataResult TcpClientCallBack::joinDataFrame(const char* buf, unsigned int size) {
//拼接数据帧,最多拼接两次
std::unique_lock<std::mutex> lk(gMx);
if (TcpBuffer::currentLen == 0) {
if (size <= 4) return JOIN_ERROR;
char pProtocol[5] = { 0 };
memcpy(pProtocol, buf, 4);
if (std::string(pProtocol).compare("PRPC")) return JOIN_ERROR;
int packBodyLen = 0;
packBodyLen |= (buf[4] & 0xFF) << 8 * 3;
packBodyLen |= (buf[5] & 0xFF) << 8 * 2;
packBodyLen |= (buf[6] & 0xFF) << 8 * 1;
packBodyLen |= (buf[7] & 0xFF) << 8 * 0;
TcpBuffer::totalLen = packBodyLen + 12;
memcpy(TcpBuffer::tcpBuf + TcpBuffer::currentLen, buf, size);
TcpBuffer::currentLen += size;
return TcpBuffer::currentLen == TcpBuffer::totalLen ? JOIN_FINISH : JOINING;
}
else if (TcpBuffer::currentLen + size < MAX_RECEIVE_BUF_LEN) {
if (TcpBuffer::currentLen + size != TcpBuffer::totalLen) return JOIN_ERROR;
memcpy(TcpBuffer::tcpBuf + TcpBuffer::currentLen, buf, size);
TcpBuffer::currentLen += size;
return JOIN_FINISH;
}
return JOIN_ERROR;
}
三、基于qt-material-widgets与qcustomplot生成界面
qt-material-widgets(github.com/laserpants/... )是material风格的控件库,qcustomplot(www.qcustomplot.com )是用于绘制图表的库。
1、qt-material-widgets与qcustomplot编译
qt-material-widgets提供的编译方式是qt5的qmake,这里采用qt6.5的cmake方式编译,需要解决两个升级qt版本的编译错误,一个是setMarigs函数的问题,还有一个是Text枚举的问题。qt6.5下QStateMachine模块分开了需要单独引入。qt6.5编译qcustomplot需要对qcustomplot.h进行如下修改。不要添加Q_MOC_RUN预定义,moc文件正常生成即可,都是编译为静态库。 工程的搭建都是通过手写CMakeLists.txt来实现,没有使用qt creator来构建工程。需要添加qt相关模块的dll,lib,头文件,及一些exe工具moc.exe,rcc.exe,uic.exe,lrelease.exe及lupdate.exe等生成moc文件,转换ui文件,资源文件及翻译文件等。qt-material-widgets编译的CMakeLists.txt如下:
bash
sset(TARGET material_components)
set(CMAKE_CXX_FLAGS "/permissive- /Zc:__cplusplus")
include(${CMAKE_INCLUDE_PATH}/qt_library.cmake)
include_qt_library()
file(GLOB moc_path "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
execute_qt_moc("components" "components" ${moc_path})
file(GLOB moc_path "${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h")
execute_qt_moc("components/lib" "components/lib" ${moc_path})
file(GLOB rcc_path "${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc")
execute_qt_rcc("components" "components" ${rcc_path})
file(GLOB HEADER
"${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
)
source_group("include" FILE ${HEADER})
file(GLOB SOURCE
"${CMAKE_CURRENT_SOURCE_DIR}/lib/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
)
source_group("source" FILE ${SOURCE})
set(SRC_LIST ${SOURCE} ${HEADER})
add_library(${TARGET} STATIC ${SRC_LIST})
set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "libmaterialComponents")
target_link_libraries(${TARGET} PRIVATE ${QT_PATH}/lib/*.lib)
scss
macro(include_qt_library)
set(QT_PATH ${PROJECT_SOURCE_DIR}/third_library/qt-6.5.2/msvc2019_64)
include_directories(${QT_PATH}/include)
include_directories(${QT_PATH}/include/QtCore)
include_directories(${QT_PATH}/include/QtGui)
include_directories(${QT_PATH}/include/QtWidgets)
include_directories(${QT_PATH}/include/QtStateMachine)
endmacro()
macro(execute_qt_moc origin_dir replace_dir ${moc_path})
foreach(H_FILE ${moc_path})
STRING(FIND ${H_FILE} "_p.h" pos)
if (pos EQUAL -1)
STRING(FIND ${H_FILE} "ui_" pos)
if (pos EQUAL -1)
set(H_FILE_R ${H_FILE})
string(REPLACE "/${origin_dir}/" "/${replace_dir}/moc_" H_FILE_R ${H_FILE_R})
string(REPLACE ".h" ".cpp" H_FILE_R ${H_FILE_R})
execute_process(COMMAND moc ${H_FILE} -o ${H_FILE_R} WORKING_DIRECTORY ${QT_PATH}/bin)
endif ()
endif ()
endforeach()
endmacro()
macro(execute_qt_uic origin_dir replace_dir ${uic_path})
foreach(UI_FILE ${uic_path})
set(H_FILE ${UI_FILE})
string(REPLACE "/${origin_dir}/" "/${replace_dir}/ui_" H_FILE ${H_FILE})
string(REPLACE ".ui" ".h" H_FILE ${H_FILE})
execute_process(COMMAND uic ${UI_FILE} -o ${H_FILE} WORKING_DIRECTORY ${QT_PATH}/bin)
endforeach()
endmacro()
macro(execute_qt_rcc origin_dir replace_dir ${rcc_path})
foreach(RCC_FILE ${rcc_path})
set(H_FILE ${RCC_FILE})
string(REPLACE "/${origin_dir}/" "/${replace_dir}/qrc_" H_FILE ${H_FILE})
string(REPLACE ".qrc" ".cpp" H_FILE ${H_FILE})
execute_process(COMMAND rcc ${RCC_FILE} -o ${H_FILE} WORKING_DIRECTORY ${QT_PATH}/bin)
endforeach()
endmacro()
macro(execute_qt_translate origin_dir replace_dir ${translate_path})
# file(REMOVE ${CMAKE_CURRENT_SOURCE_DIR}/source/ZH_CN.ts)
foreach(translate_FILE ${translate_path})
set(TS_FILE ${translate_FILE})
string(REPLACE "/${origin_dir}" "/${replace_dir}" TS_FILE ${TS_FILE})
string(REPLACE ".cpp" ".ts" TS_FILE ${TS_FILE})
execute_process(COMMAND lupdate ${translate_FILE} -ts ${TS_FILE} WORKING_DIRECTORY ${QT_PATH}/bin)
set(QM_FILE ${TS_FILE})
string(REPLACE ".ts" ".qm" QM_FILE ${QM_FILE})
execute_process(COMMAND lrelease ${TS_FILE} -qm ${QM_FILE} WORKING_DIRECTORY ${QT_PATH}/bin)
endforeach()
endmacro()
2、效果展示
按钮是采用了qt-material-widgets库的QtMaterialFlatButton,带有动态效果;图表是采用了qcustomplot库的axis-tags例子做了一个动态加载图。