C++生成JSON字符串性能疑问:前置snprintf为何降延迟?求高效方案
问题:重复snprintf语句为何降低延迟?更高效的JSON字符串生成方法?
我需要向仅接受JSON字符串的服务器发送请求,因此需生成仅部分关键字不同的字符串,且对延迟极为敏感。在搭载i9 10900k的机器上,使用编译命令g++ -O3 main.cpp -o main编译测试代码后,若保留第一条长snprintf语句,目标snprintf的延迟低于1us;移除该语句后,延迟升至5us。测试代码如下:
#include <stdio.h> #include <sys/time.h> int main() { char body[256]; double qty = 1; // the following line make a big difference, 5us if remove it, else < 1us !!! snprintf(body, sizeof(body), "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\"%s\", \"price\":\"%s\", \"force\": \"%s\", \"clientOid\": \"%s\", \"side\": \"%s\", \"symbol\": \"%s\"}", "huangxinyu", "0.1", "0.11112", "asdasdas", "", (qty > 0 ? "sskiii" : "sell")); // !!!!!!!!! this line make a huge difference in latency snprintf(body, sizeof(body), "asdasdasdasd"); timeval t; gettimeofday(&t, NULL); int usec = t.tv_usec; snprintf(body, sizeof(body), "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\"%s\", \"price\":\"%s\", \"force\": \"%s\", \"clientOid\": \"%s\", \"side\": \"%s\", \"symbol\": \"%s\"}", "asd", "0.1111", "0.11112", "ioc", "", (qty > 0 ? "buy" : "sell")); gettimeofday(&t, NULL); printf("cost %ld\n", t.tv_usec - usec); }
一、为何添加第一条snprintf会降低延迟?
- CPU缓存预热:第一条snprintf执行时,会把JSON格式的常量字符串、snprintf函数的代码逻辑、以及
body数组所在的内存页加载到CPU的L1/L2高速缓存中。当目标snprintf执行时,这些资源已经在缓存里,无需从主内存甚至磁盘加载,因此延迟大幅降低。如果移除第一条语句,目标snprintf是首次执行相关逻辑,需要冷加载资源,导致延迟升高。 - 分支预测与编译器优化:
-O3优化下,第一条snprintf会让CPU提前学习到(qty > 0 ? ... : ...)这个分支的走向,减少分支预测失败的开销;同时编译器可能会对重复的格式字符串解析做优化,生成更高效的机器码,让后续相同格式的snprintf执行更快。
二、更高效的JSON字符串生成方法
1. 预模板+内存拷贝替换
把JSON的固定部分提前存为模板,可变字段直接通过内存拷贝替换到对应位置,完全跳过格式解析的开销:
#include <string.h> #include <stdio.h> int main() { char body[256]; // 预定义模板,可变位置预留足够空间 const char template[] = "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\"\", \"price\":\"\", \"force\":\"\", \"clientOid\":\"\", \"side\":\"\", \"symbol\":\"\"}"; memcpy(body, template, sizeof(template)-1); // 替换size字段 char* pos = strstr(body, "\"size\":\"") + 7; const char* size_val = "asd"; memcpy(pos, size_val, strlen(size_val)); pos[strlen(size_val)] = '"'; // 替换price字段 pos = strstr(body, "\"price\":\"") + 8; const char* price_val = "0.1111"; memcpy(pos, price_val, strlen(price_val)); pos[strlen(price_val)] = '"'; // 依次替换其他字段... }
2. 手动栈上拼接
如果字段长度可提前预估,直接手动拼接字符串,避免格式化函数的额外开销:
#include <stdio.h> int main() { char body[256]; int offset = 0; offset += sprintf(body + offset, "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\""); offset += sprintf(body + offset, "%s", "asd"); offset += sprintf(body + offset, "\", \"price\":\""); offset += sprintf(body + offset, "%s", "0.1111"); offset += sprintf(body + offset, "\", \"force\":\""); offset += sprintf(body + offset, "%s", "0.11112"); offset += sprintf(body + offset, "\", \"clientOid\":\""); offset += sprintf(body + offset, "%s", "ioc"); offset += sprintf(body + offset, "\", \"side\":\""); offset += sprintf(body + offset, "%s", ""); offset += sprintf(body + offset, "\", \"symbol\":\""); offset += sprintf(body + offset, "%s", "buy"); offset += sprintf(body + offset, "\"}"); }
3. 编译期生成(C++)
如果部分可变字段在编译期已知,可利用C++的constexpr在编译期生成完整JSON字符串,运行时直接使用:
#include <array> #include <string_view> constexpr std::array<char, 256> build_json(std::string_view size, std::string_view price) { std::array<char, 256> buf{}; int idx = 0; // 手动拼接固定部分和可变部分 auto append = [&](std::string_view s) { for (char c : s) buf[idx++] = c; }; append("{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\""); append(size); append("\", \"price\":\""); append(price); append("\", \"force\":\"0.11112\", \"clientOid\":\"ioc\", \"side\":\"\", \"symbol\":\"buy\"}"); buf[idx] = '\0'; return buf; } int main() { constexpr auto json = build_json("asd", "0.1111"); // 直接使用json.data()即可 }
4. 预生成常用变体
如果业务中常用的JSON变体有限,提前把所有可能的JSON字符串预先生成好,运行时直接选择对应的字符串,实现零开销。
内容的提问来源于stack exchange,提问作者nick
相关产品推荐
相关产品推荐

