You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)的优化

  1. 修正返回值逻辑
    当前返回的是不等位的掩码,和标准strcmp的返回值(负数/0/正数)不符,既不符合功能预期,也会导致编译器无法做等价优化。需找到第一个不等字节,计算差值返回。
  2. 处理字符串终止符
    原实现直接加载32字节,但字符串可能提前结束,需同时检查终止符。合并“字节不等”和“遇到终止符”的判断逻辑,避免越界比较。
  3. 优化后的实现示例
    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]);
    }
    
  4. 启用内联优化
    将函数标记为static inline,让编译器在调用处展开,消除函数调用的栈开销。

针对汇编版本(my_strcmp2)的优化

  1. 放弃逐字节比较
    逐字节比较是性能极差的核心原因,改用AVX2向量指令一次比较32字节,大幅减少循环次数和分支开销。
  2. 优化后的汇编实现示例
    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常量
    

通用优化点

  1. 测试场景覆盖
    当前测试用例的差异出现在第10个字符附近,标准库strcmp有短字符串优化逻辑。建议补充测试多种场景:前几个字符就不同、完全相同、刚好32字符等,确保优化效果全面。
  2. 对齐优化
    如果输入字符串保证16字节对齐,可将vmovdqu替换为vmovdqa,提升内存加载效率;若不对齐,vmovdqu已是最优选择。
  3. 编译器优化配合
    保持-march=znver3 -O3编译选项,让编译器自动做指令调度和冗余代码消除。

内容的提问来源于stack exchange,提问作者A_Elbereth_GIlthoniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:32:07