如何复用字符串缓冲区替换关键词,降低C++字符串生成延迟?
复用固定格式字符串缓冲区降低C++格式化延迟的方案
你的问题核心是避免每次全量调用snprintf解析整个大格式字符串——snprintf需要逐个字符解析格式占位符,对于大字符串来说开销极高。下面是几种高效的优化方案,核心思路都是复用固定部分,只更新可变字段:
方案1:预构建模板缓冲区+标记可变位置
先一次性生成包含预留占位符的模板缓冲区,记录每个可变字段的起始位置和预留长度,后续每次只需复制模板,然后在对应位置写入新值即可:
步骤说明:
- 初始化模板与位置标记:程序启动时生成一次模板,用足够长的占位符(比如
"XXXX...")占据可变字段的位置,同时记录每个字段的起始偏移和最大长度。 - 复用模板更新字段:每次需要生成新字符串时,先通过
memcpy快速复制模板到工作缓冲区,再针对每个可变字段做小范围的格式化写入,避免全量解析。
代码示例:
#include <cstdio> #include <cstring> // 记录每个可变字段的位置和预留长度 struct FieldOffsets { size_t qty_start; size_t qty_max_len; size_t price_start; size_t price_max_len; size_t clientOid_start; size_t clientOid_max_len; size_t side_start; size_t side_max_len; size_t symbol_start; size_t symbol_max_len; }; char g_body_template[256]; FieldOffsets g_offsets; // 初始化模板(仅调用一次,比如程序启动时) void init_template() { // 生成带占位符的模板,预留足够空间给可变字段 snprintf(g_body_template, sizeof(g_body_template), "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", " "\"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\"%*s\", " "\"price\":\"%*s\", \"force\": \"ioc\", \"clientOid\": \"%*s\", " "\"side\": \"%*s\", \"symbol\": \"%*s\"}", 20, "", 20, "", 32, "", 4, "", 10, ""); // 定位每个可变字段的起始位置 g_offsets.qty_start = strstr(g_body_template, "\"size\":\"\"") + 7; // 跳过"size":" g_offsets.qty_max_len = 20; g_offsets.price_start = strstr(g_body_template, "\"price\":\"\"") + 8; // 跳过"price":" g_offsets.price_max_len = 20; g_offsets.clientOid_start = strstr(g_body_template, "\"clientOid\":\"\"") + 12; // 跳过"clientOid":" g_offsets.clientOid_max_len = 32; g_offsets.side_start = strstr(g_body_template, "\"side\":\"\"") + 7; // 跳过"side":" g_offsets.side_max_len = 4; g_offsets.symbol_start = strstr(g_body_template, "\"symbol\":\"\"") + 9; // 跳过"symbol":" g_offsets.symbol_max_len = 10; } // 快速生成新字符串 void generate_body(double qty, double price, const char* clientOid, const char* symbol, char* out_body) { // 1. 快速复制模板到输出缓冲区(memcpy比snprintf快几个数量级) memcpy(out_body, g_body_template, sizeof(g_body_template)); // 2. 更新qty字段(用sprintf替代std::to_string减少临时对象开销) char qty_buf[32]; snprintf(qty_buf, sizeof(qty_buf), "%.1f", qty); // 按需调整精度 strncpy(out_body + g_offsets.qty_start, qty_buf, g_offsets.qty_max_len); // 3. 更新price字段 char price_buf[32]; snprintf(price_buf, sizeof(price_buf), "%.0f", price); strncpy(out_body + g_offsets.price_start, price_buf, g_offsets.price_max_len); // 4. 更新clientOid字段 strncpy(out_body + g_offsets.clientOid_start, clientOid, g_offsets.clientOid_max_len); // 5. 更新side字段 const char* side_str = (qty > 0) ? "buy" : "sell"; strncpy(out_body + g_offsets.side_start, side_str, g_offsets.side_max_len); // 6. 更新symbol字段 strncpy(out_body + g_offsets.symbol_start, symbol, g_offsets.symbol_max_len); } // 使用示例 int main() { init_template(); char body[256]; generate_body(1.0, 123123.0, "asdasdas", "BTC_USDT", body); return 0; }
方案2:拆分固定片段+直接拼接
把原固定字符串拆分为多个不可变的片段,每次生成新字符串时,直接将固定片段与可变内容依次复制到缓冲区,完全避免格式解析开销:
代码示例:
#include <cstdio> #include <cstring> // 预定义固定字符串片段 const char* PART1 = "{\"productType\": \"usdt-futures\", \"marginMode\": \"crossed\", \"orderType\": \"limit\", \"marginCoin\": \"USDT\", \"size\":\""; const char* PART2 = "\", \"price\":\""; const char* PART3 = "\", \"force\": \"ioc\", \"clientOid\": \""; const char* PART4 = "\", \"side\": \""; const char* PART5 = "\", \"symbol\": \""; const char* PART6 = "\"}"; void generate_body(double qty, double price, const char* clientOid, const char* symbol, char* out_body) { char* ptr = out_body; // 拼接固定片段1 size_t len = strlen(PART1); memcpy(ptr, PART1, len); ptr += len; // 拼接qty char qty_buf[32]; len = snprintf(qty_buf, sizeof(qty_buf), "%.1f", qty); memcpy(ptr, qty_buf, len); ptr += len; // 拼接固定片段2 len = strlen(PART2); memcpy(ptr, PART2, len); ptr += len; // 拼接price char price_buf[32]; len = snprintf(price_buf, sizeof(price_buf), "%.0f", price); memcpy(ptr, price_buf, len); ptr += len; // 拼接固定片段3 len = strlen(PART3); memcpy(ptr, PART3, len); ptr += len; // 拼接clientOid len = strlen(clientOid); memcpy(ptr, clientOid, len); ptr += len; // 拼接固定片段4 len = strlen(PART4); memcpy(ptr, PART4, len); ptr += len; // 拼接side const char* side_str = (qty > 0) ? "buy" : "sell"; len = strlen(side_str); memcpy(ptr, side_str, len); ptr += len; // 拼接固定片段5 len = strlen(PART5); memcpy(ptr, PART5, len); ptr += len; // 拼接symbol len = strlen(symbol); memcpy(ptr, symbol, len); ptr += len; // 拼接固定片段6 len = strlen(PART6); memcpy(ptr, PART6, len); ptr += len; // 确保字符串结尾 *ptr = '\0'; } // 使用示例 int main() { char body[256]; generate_body(1.0, 123123.0, "asdasdas", "BTC_USDT", body); return 0; }
额外优化点
- 避免
std::to_string:该函数会生成临时std::string对象,有构造/销毁开销,改用sprintf到局部char数组更高效。 - 固定缓冲区大小:提前计算好最大可能的字符串长度,确保缓冲区足够,避免溢出。
- 预初始化模板:模板或固定片段只在程序启动时初始化一次,不要每次生成字符串都重新构建。
这两种方案都能将每次生成字符串的延迟从几十微秒降到微秒级甚至更低,完全复用了固定部分的内容,只处理可变字段。
内容的提问来源于stack exchange,提问作者nick
相关产品推荐
相关产品推荐

