一、函數功能概述
sds sdscatrepr(sds s, const char *p, size_t len)
函數的核心功能是將字符串p
追加到字符串s
中。在追加過程中,它會對字符串p
中的字符進行判斷,使用isprint()
函數識別不可打印字符,并對這些字符進行轉義處理,確保最終追加后的字符串s
符合特定的格式要求 。
此函數主要用在Monitor模式下。
二、原始版本代碼分析
sds sdscatrepr(sds s, const char *p, size_t len) {s = sdscatlen(s,"\"",1);while(len--) {switch(*p) {case '\\':case '"':s = sdscatprintf(s,"\\%c",*p);break;case '\n': s = sdscatlen(s,"\\n",2); break;case '\r': s = sdscatlen(s,"\\r",2); break;case '\t': s = sdscatlen(s,"\\t",2); break;case '\a': s = sdscatlen(s,"\\a",2); break;case '\b': s = sdscatlen(s,"\\b",2); break;default:if (isprint(*p))s = sdscatprintf(s,"%c",*p);elses = sdscatprintf(s,"\\x%02x",(unsigned char)*p);break;}p++;}return sdscatlen(s,"\"",1);
}
2.1 實現邏輯
- 首先在字符串
s
末尾追加雙引號"
。 - 然后通過
while
循環遍歷字符串p
,對每個字符進行判斷:
- 若字符是
\
或"
,使用sdscatprintf
函數將其轉義后追加到s
中。 - 若字符是換行符
\n
、回車符\r
、制表符\t
、響鈴符\a
、退格符\b
,分別使用sdscatlen
函數將對應的轉義序列追加到s
中。 - 對于其他字符,先判斷是否為可打印字符。如果是,使用
sdscatprintf
函數以格式化字符形式追加;如果不是,將其以十六進制字符串形式追加到s
中。
- 遍歷結束后,在字符串
s
末尾再追加一個雙引號"
。
2.2 性能瓶頸
-
頻繁內存申請:目標字符串
s
的可用空間不一定能容納字符串p
,在執行sdscatlen
和sdscatprintf
函數時,若空間不足會觸發s
的重新空間申請。當字符串p
較長時,頻繁的空間申請操作會嚴重影響函數性能。 -
可打印字符處理低效:對于可打印字符,使用
sdscatprintf(s,"%c",*p);
進行格式化追加,相比直接賦值操作,這種方式會調用vsnprintf
函數,額外的函數調用和格式化處理消耗了大量性能。
三、預分配版本代碼分析
sds sdscatrepr(sds s, const char *p, size_t len) {s = sdsMakeRoomFor(s, len + 2);s = sdscatlen(s,"\"",1);while(len--) {switch(*p) {case '\\':case '"':s = sdscatprintf(s,"\\%c",*p);break;case '\n': s = sdscatlen(s,"\\n",2); break;case '\r': s = sdscatlen(s,"\\r",2); break;case '\t': s = sdscatlen(s,"\\t",2); break;case '\a': s = sdscatlen(s,"\\a",2); break;case '\b': s = sdscatlen(s,"\\b",2); break;default:if (isprint(*p))s = sdscatlen(s, p, 1);elses = sdscatprintf(s,"\\x%02x",(unsigned char)*p);break;}p++;}return sdscatlen(s,"\"",1);
}
3.1 優化思路
-
空間預分配:在函數入口處,通過
s = sdsMakeRoomFor(s, len + 2);
提前為字符串s
分配足夠的空間,其中+2
是為了容納首尾的雙引號。這樣在大多數情況下,后續追加字符串p
時,不會頻繁觸發s
的內存重新申請,減少了內存操作的開銷。 -
可打印字符處理優化:將處理可打印字符的
s = sdscatprintf(s,"%c",*p);
替換為s = sdscatlen(s, p, 1);
,避免了調用vsnprintf
函數,直接使用memcpy
進行字符復制,提高了可打印字符追加的效率。
3.2 優化效果
測試場景 | 吞吐量(requests per second) | 平均延遲(msec) | 最小延遲(msec) | 50% 分位延遲(msec) | 95% 分位延遲(msec) | 99% 分位延遲(msec) | 最大延遲(msec) |
---|---|---|---|---|---|---|---|
優化前(無監控) | 264669.28 | 1.663 | 0.304 | 1.623 | 2.487 | 3.383 | 45.855 |
優化前(1 個監控) | 78633.66 | 5.917 | 0.312 | 4.815 | 11.711 | 31.775 | 171.391 |
優化后(無監控) | 255761 | 1.710 | 0.320 | 1.631 | 2.727 | 3.579 | 36.415 |
優化后(1 個監控) | 106142.47 | 4.347 | 0.328 | 3.695 | 7.783 | 18.031 | 107.711 |
從結果看吞吐從788633.66/s提升到了106142.47,提升幅度約34%。
3.3 仍存在的問題
-
盡管進行了空間預分配,但當字符串
p
中存在大量不可見字符時,由于對不可見字符的處理方式仍可能導致空間不足,進而觸發s
的內存重新申請,影響性能。 -
sdscatlen
函數分析
sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s);s = sdsMakeRoomFor(s, len);if (s == NULL) return NULL;memcpy(s + curlen, t, len);sdssetlen(s, curlen + len);s[curlen + len] = '\0';return s;
}
從該函數邏輯可以看出,對于可打印字符,在使用sdscatlen
函數追加時,每追加一個字符就會調用一次memcpy
函數。當處理長字符串p
時,頻繁調用memcpy
會帶來一定的性能損耗。
四、批處理版本代碼分析
sds sdscatrepr(sds s, const char *p, size_t len) {s = sdsMakeRoomFor(s, len + 2);s = sdscatlen(s, "\"", 1);while (len) {if (isprint(*p)) {const char *start = p;while (len && isprint(*p)) {len--;p++;}s = sdscatlen(s, start, p - start);} else {switch (*p) {case '\\':case '"': s = sdscatprintf(s, "\\%c", *p); break;case '\n': s = sdscatlen(s, "\\n", 2); break;case '\r': s = sdscatlen(s, "\\r", 2); break;case '\t': s = sdscatlen(s, "\\t", 2); break;case '\a': s = sdscatlen(s, "\\a", 2); break;case '\b': s = sdscatlen(s, "\\b", 2); break;default:s = sdscatprintf(s, "\\x%02x", (unsigned char)*p);break;}p++;len--;}}return sdscatlen(s, "\"", 1);
}
4.1 優化邏輯
-
調整判斷順序:在遍歷字符串
p
時,優先判斷字符是否為可打印字符。如果是,以當前字符為起始點,繼續判斷后續字符是否也為可打印字符,直到遇到非可打印字符或字符串結束。 -
批量追加:通過記錄可打印字符的起始位置和長度,使用
sdscatlen(s, start, p - start);
一次性將連續的可打印字符追加到字符串s
中。這種方式減少了memcpy
函數的調用次數,相比原始版本和預分配版本,進一步提高了可打印字符追加的效率,尤其在處理包含大量連續可打印字符的字符串時,性能提升更為顯著。
4.2 優化效果
測試場景 | 吞吐量(requests per second) | 平均延遲(msec) | 最小延遲(msec) | 50% 分位延遲(msec) | 95% 分位延遲(msec) | 99% 分位延遲(msec) | 最大延遲(msec) |
---|---|---|---|---|---|---|---|
優化前(無監控) | 714081.69 | 0.654 | 0.112 | 0.687 | 0.847 | 0.879 | 3.247 |
優化前(1 個監控) | 332967.06 | 1.449 | 0.384 | 1.647 | 1.855 | 1.927 | 4.263 |
優化后(無監控) | 714030.69 | 0.645 | 0.128 | 0.671 | 0.839 | 0.871 | 2.839 |
優化后(1 個監控) | 395116.38 | 1.221 | 0.248 | 1.367 | 1.575 | 1.631 | 3.735 |
通過對比可以發現,在有1個監控的場景下,優化后的版本吞吐量從332967.06/s提升至395116.38/s,提升幅度約為18%,且各分位延遲也有所改善,充分證明了批量處理可打印字符的優化策略對sdscatrepr函數性能提升的有效性。