在圖像處理、科學計算等計算密集型應用中,頻繁對大塊內存進行初始化(歸零或賦值)是常態。受限於內存帶寬與 CPU 之間的速度瓶頸,標準庫 memset 或 Windows ZeroMemory 往往成為性能熱點。雖然現代編譯器(VC++)提供優化選項,但生成的代碼往往未能充分利用 SIMD 指令集(MMX/SSE/SSE2),導致性能遠低於處理器理論峰值。
項目需在多線程並發場景下初始化 100MB 內存,循環 60 次。測試環境:Release 模式,雙線程並行。
#define SIZE (1024*1024*100) // 100MB
#define LoopTimes 60
#define THREADS 2
void threadfunc(void* dummy) {
LPBYTE lpByte = (LPBYTE)dummy;
DWORD dwStart, dwTime1, dwTime2;
// Intel 優化版 memset
dwStart = GetTickCount();
for (int j=0; j<LoopTimes; j++)
__intel_new_memset(lpByte, 1, SIZE);
dwTime1 = GetTickCount() - dwStart;
// MS CRT 標準 memset
dwStart = GetTickCount();
for (int j=0; j<LoopTimes; j++)
memset(lpByte, 1, SIZE);
dwTime2 = GetTickCount() - dwStart;
printf("Intel=%dms MSC=%dms\n", dwTime1, dwTime2);
}
int main() {
HANDLE hThread[THREADS];
LPBYTE lpByte[THREADS];
// 分配內存、創建線程、等待結束... (略)
}
| 實現版本 | 耗時範圍 | 性能提升 |
|---|---|---|
MS CRT memset | 12.45 ~ 12.55 秒 | 基準 |
Intel C++ Compiler memset | 4.38 ~ 4.53 秒 | 約 2.9 倍 |
在內存帶寬受限的多線程場景下,優化版將初始化時間縮短近 2/3,對整體吞吐率提升顯著。
通過反彙編查看循環調用處的機器碼:
; 循環調用 memset
push 6400000h ; SIZE
push 1 ; value
push ebx ; lpByte
call _memset ; 庫函數調用開銷
add esp, 0Ch
dec esi
jne LoopStart
內部實現通常為逐字節/逐字存儲,未向量化。
; 循環調用 __intel_new_memset
push 6400000h
push 1
push ebx
call ___intel_new_memset
add esp, 0Ch
dec esi
jne LoopStart
雖然調用框架相似,但 __intel_new_memset 內部大量使用 movdqa (SSE2 128位對齊移動)、movntdq (非時序存儲避免缓存污染) 等指令,實現每時鐘周期處理 16/32 字節,並針對不同對齊情況生成多條優化路徑。
避免整個專案遷移到 Intel C++ Compiler (ICC) 的風險,採用「庫提取法」:
memset 等關鍵函數的獨立源文件,生成靜態庫 intelmem.libextern "C" void* __cdecl __intel_new_memset(void*, int, size_t);#pragma comment(lib, "intelmem.lib")__intel_new_memset此法零侵入業務邏輯,風險可控,效果立竿見影。
memcpy、memmove 等內存操作函數_mm_malloc 確保內存 32/64 字節對齊,發揮向量指令最大效能movntdq/movntps) 減少緩存污染標準庫並非總是最優。在性能關鍵路徑上,深入理解硬件特性(SIMD、緩存行、非時序存儲),結合專用編譯器生成的優化庫,往往能獲得數倍收益。這正是「知其然,更知其所以然」的工程智慧體現。