STRCMP函数优化求助:自定义实现性能未达预期求改进方案
针对32字符限制场景的strcmp优化问题及优化建议
问题背景
当前正在针对长度≤32字符的字符串场景优化C语言strcmp函数,实现了两种版本,但测试性能远低于标准库strcmp:
- 标准
strcmp:耗时0.000002秒 - Intrinsics实现的
my_strcmp:耗时0.000851秒 - 汇编实现的
my_strcmp2:耗时0.017444秒
编译命令:
nasm -g -f elf64 my_strcmp2.asm -o my_strcmp2.o g++ my_strcmp2.o main.cpp -march=znver3 -O3 -o app
现有实现代码
main.cpp
#include <stdio.h> #include <string.h> #include <time.h> #include <immintrin.h> int my_strcmp(const char* word1, const char* word2); extern "C" int my_strcmp2(const char* word1, const char* word2); double timer(int (*func)(const char*, const char*)); int main() { int j = 0; const char* word1 = "my name is"; const char* word2 = "my name are"; clock_t begin = clock(); for (int i = 0; i < 1000000; i++) { j = strcmp(word1, word2); } clock_t end = clock(); fprintf(stderr, "strcmp : %lf seconds\n", (double)(end - begin) / CLOCKS_PER_SEC); begin = clock(); for (int i = 0; i < 1000000; i++) { j = my_strcmp(word1, word2); } end = clock(); fprintf(stderr, "my_strcmp : %lf seconds\n", (double)(end - begin) / CLOCKS_PER_SEC); begin = clock(); for (int i = 0; i < 1000000; i++) { j = my_strcmp2(word1, word2); } end = clock(); fprintf(stderr, "my_strcmp2 : %lf seconds\n", (double)(end - begin) / CLOCKS_PER_SEC); return 0; } int my_strcmp(const char* word1, const char* word2) { __m256i str_reg_1 = _mm256_lddqu_si256((__m256i*)word1); __m256i str_reg_2 = _mm256_lddqu_si256((__m256i*)word2); __m256i res_cmp = _mm256_cmpeq_epi8(str_reg_1, str_reg_2); int mask = ~_mm256_movemask_epi8(res_cmp); return mask; }
my_strcmp2.asm
global my_strcmp2 section .text my_strcmp2: cycle: cmp byte [rel rdi], 0x00 je check_equal mov dl, byte [rel rsi] cmp byte [rel rdi], dl jne not_equal add rdi, 1 add rsi, 1 jmp cycle not_equal: mov rax, 0x01 ret check_equal: cmp byte [rel rsi], 0x00 jne not_equal xor rax, rax ret
优化建议
针对Intrinsics版本(my_strcmp)的优化
- 修正返回值逻辑
当前返回的是不等位的掩码,和标准strcmp的返回值(负数/0/正数)不符,既不符合功能预期,也会导致编译器无法做等价优化。需找到第一个不等字节,计算差值返回。 - 处理字符串终止符
原实现直接加载32字节,但字符串可能提前结束,需同时检查终止符。合并“字节不等”和“遇到终止符”的判断逻辑,避免越界比较。 - 优化后的实现示例
int my_strcmp(const char* a, const char* b) { __m256i va = _mm256_lddqu_si256((const __m256i*)a); __m256i vb = _mm256_lddqu_si256((const __m256i*)b); // 比较字节相等性 __m256i eq_mask = _mm256_cmpeq_epi8(va, vb); // 检查两个字符串是否出现终止符 __m256i zero = _mm256_setzero_si256(); __m256i a_end = _mm256_cmpeq_epi8(va, zero); __m256i b_end = _mm256_cmpeq_epi8(vb, zero); __m256i end_mask = _mm256_or_si256(a_end, b_end); // 合并“不等”和“终止符”的掩码,找到第一个需要处理的位置 int mask = _mm256_movemask_epi8(_mm256_or_si256(_mm256_not_si256(eq_mask), end_mask)); if (mask == 0) return 0; // 32字节全相等且无终止符(场景限制下不可能) // 找到第一个差异位,计算差值 int first_diff = __builtin_ctz(mask); return ((unsigned char)a[first_diff] - (unsigned char)b[first_diff]); } - 启用内联优化
将函数标记为static inline,让编译器在调用处展开,消除函数调用的栈开销。
针对汇编版本(my_strcmp2)的优化
- 放弃逐字节比较
逐字节比较是性能极差的核心原因,改用AVX2向量指令一次比较32字节,大幅减少循环次数和分支开销。 - 优化后的汇编实现示例
global my_strcmp2 section .text my_strcmp2: ; 加载32字节字符串到向量寄存器 vmovdqu ymm0, [rdi] vmovdqu ymm1, [rsi] ; 比较字节相等性 vpcmpeqb ymm2, ymm0, ymm1 ; 检查终止符 vpcmpeqb ymm3, ymm0, ymmword [rel zero] vpcmpeqb ymm4, ymm1, ymmword [rel zero] vorps ymm3, ymm3, ymm4 ; 合并不等和终止符的掩码,取反后找第一个置位位 vorps ymm2, ymm2, ymm3 vpmovmskb eax, ymm2 not eax test eax, eax jz .equal ; 找到第一个差异位置,计算字节差值 bsf eax, eax movzx ecx, byte [rdi + rax] movzx edx, byte [rsi + rax] sub ecx, edx mov eax, ecx ret .equal: xor eax, eax ret section .data zero: dq 0, 0, 0, 0 ; 256位全0常量
通用优化点
- 测试场景覆盖
当前测试用例的差异出现在第10个字符附近,标准库strcmp有短字符串优化逻辑。建议补充测试多种场景:前几个字符就不同、完全相同、刚好32字符等,确保优化效果全面。 - 对齐优化
如果输入字符串保证16字节对齐,可将vmovdqu替换为vmovdqa,提升内存加载效率;若不对齐,vmovdqu已是最优选择。 - 编译器优化配合
保持-march=znver3 -O3编译选项,让编译器自动做指令调度和冗余代码消除。
内容的提问来源于stack exchange,提问作者A_Elbereth_GIlthoniel
相关产品推荐
相关产品推荐

