strlen是否未存于DLL/静态库?自定义实现为何小字符串更快?
我对比了自定义strlen实现与MSVC标准库strlen的性能,自定义实现代码如下:
inline ui64 Sl(const char *cs) { const char *sbeg = cs--; while(*(++cs) != 0) { ; } return ui64(cs - sbeg); }
测试方式是通过带volatile局部变量的__declspec(noinline)包装函数调用两者,在未优化循环中执行数百万次。结果显示:自定义函数在短字符串上快15-20%,长字符串(1024+)性能相当。
1. MSVC的strlen是编译器生成而非库函数?
是的,MSVC中的strlen属于编译器内置函数,而非单纯从DLL或静态库中调用预编译实现:
string.h中的声明是符合C标准的接口占位,但编译器处理代码时,会根据上下文直接生成优化后的代码:- 针对字符串字面量,编译器会在编译期直接计算长度,将
strlen("xxx")替换为常量值,这是常量折叠优化的一部分; - 针对动态字符串,MSVC会内联高效的汇编实现(可能包含SIMD、循环展开等优化),而非调用外部库函数。
- 针对字符串字面量,编译器会在编译期直接计算长度,将
这种设计是为了最大化性能——编译器能根据具体场景生成最适配的代码,比通用的库函数调用更高效。
2. 自定义函数短字符串更快的原因
从指令序列和CPU流水线效率的角度分析:
核心指令对比
标准库strlen的典型内联汇编逻辑:
mov rdx, rcx ; rdx = 字符串起始地址 loop_start: cmp byte ptr [rdx], 0 je loop_end ; 遇到终止符就退出循环 inc rdx jmp loop_start ; 无条件跳转回循环开头 loop_end: sub rdx, rcx ret自定义
Sl函数的汇编逻辑:mov rdx, rcx ; sbeg = cs dec rdx ; cs-- loop_start: inc rdx ; ++cs cmp byte ptr [rdx], 0 jne loop_start ; 非终止符则继续循环 sub rdx, rcx ret
关键性能差异点
循环内分支开销:
标准库循环包含一个条件跳转(je)和一个无条件跳转(jmp),每次迭代需要两次跳转操作;自定义函数仅用一个条件跳转(jne),指令序列更紧凑,减少了跳转带来的流水线停顿。指令并行性:
自定义函数循环中,inc rdx和cmp byte ptr [rdx], 0可被CPU流水线并行执行;而标准库循环中,cmp的结果直接决定是否执行inc,指令依赖更强,并行执行空间更小。循环前额外操作的影响:
自定义函数循环前多了一条dec rdx指令,但这是一次性开销,对于短字符串(迭代次数少)来说,单次指令的开销远小于循环内每次迭代的分支和指令依赖损耗,因此整体更快。
对于长字符串,循环迭代次数足够多,循环前的一次性开销可忽略,且标准库内联实现会启用SIMD或循环展开优化,抵消了循环内的指令差异,因此两者性能相当。
内容的提问来源于stack exchange,提问作者ScienceDiscoverer

