在單機 2 千元級「山寨服務器」上支撐全鏈路搜索引擎 (抓取、分詞、索引、去重、聚類、壓縮、排序、Web 渲染),字符串處理佔 90%+ CPU 時間。本文揭秘從 std::string/CString 遷移到自研 BlueString 的五大優化戰役,並展望「零內存」字符串前沿。
痛點:標準 new/delete 全局鎖 + 長期運行產生碎片 → 分配延遲抖動、甚至 OOM。
方案:
HeapCreate(HEAP_NO_SERIALIZE) 私有堆,分配/釋放零鎖。效果:分配延遲從 μs 級降至 10ns 級,長時運行零碎片。
痛點:函數參數/返回值傳遞、STL 容器擴容觸發深拷貝。
方案:
std::atomic (C++11) 或 GCC 內建 __sync_fetch_and_add,無鎖線程安全。c_str()) 直接共享緩衝區。operator[]/append/Format) 觸發 detach() 實際拷貝。關鍵:拒絕全局鎖 COW (如舊版 std::string),否則多線程下鎖爭用比拷貝更慢。
GetFormattedLength 雙遍歷痛點:MFC CString::Format 內部先 vsnprintf(NULL,0) 計長度,再實寫 — 雙倍格式化開銷。
方案:
// 新增可選參數 estLen,調用方預估最大長度
void Format(const char* fmt, ..., size_t estLen = 0);
void AppendFormat(const char* fmt, ..., size_t estLen = 0);
estLen = 1024) → 直接預留緩衝,跳過計長步驟。痛點:Web 頁面模板渲染涉及數十次 +=,每次觸發重新分配、拷貝、釋放。
方案:
reserve(estimated_total) 一次性預留大塊 (空間換時間)。std::vector fragments :+= 只 push_back 指針+長度,不拷貝數據。c_str() / 显式 flatten()) 時,一次性 memcpy 合併到主緩衝區。實測:百 KB 頁面渲染從 2ms 降至 0.2ms。
痛點:關鍵詞/查詢詞多為 < 32 字節,若通用「預留 1KB」策略 → 內存放大 30x。
方案:
+16 bytes 精細增長。對只讀、壽命長、極大的數據 (如靜態詞典、倒排列表),不分配任何新內存,直接持有 mmap 映射區的 StringView (指針+長度+偏移),支持切片、查找、樹構建索引。適用場景窄但威力巨大:關鍵詞 Trie 樹構建零拷貝,內存再降 40%。
| 階段 | 動作 | 驗證指標 |
|---|---|---|
| 1 | 接入私有堆 + 無鎖 COW 基類 | 分配延遲 P99 < 50ns |
| 2 | 重寫 Format/AppendFormat 加 estLen | 格式化吞吐 ≥ 2x |
| 3 | += 改為片段鏈表 + flatten | 大串拼接 10x |
| 4 | SSO + 小塊專用池 | 小串內存 -70% |
| 5 | 關鍵只讀路徑引入 StringView/mmapped | 總內存再降 30%+ |
來源:藍蜂博客