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

为何SIMD+PEXT实现的八进制字符串解析性能不及编译器优化版本?

八进制字符串解析器:编译器优化 vs 自定义SIMD实现性能分析

我为学习SIMD和intrinsics技术,编写了一个满足以下条件的八进制字符串解析器:

  • ASCII编码
  • 固定长度为12字节
  • 非空终止
  • 前缀补零
  • 保证格式合法

原始非向量化实现

uint64_t parseOctalToBase10(const char message[12]) {
    uint64_t size = 0;
    for (int i = 0; i < 12; i++) {
        uint64_t value = message[i];
        value -= '0';
        size |= value << (3 * ((12 - 1) - i));
    }
    return size;
}

编译命令

使用Clang 19.1.7在Linux上编译:

clang++ -std=c++26 -O3 -mavx512vl -mavx512bw -mbmi2 -S example.cpp -o example.s

生成的汇编代码

_Z18parseOctalToBase10PKc:              # @_Z18parseOctalToBase10PKc
    .cfi_startproc
# %bb.0:
    vpmovsxbq   (%rdi), %zmm0
    vpsllvq .LCPI0_0(%rip), %zmm0, %zmm0
    vpaddq  .LCPI0_1(%rip), %zmm0, %zmm0
    movsbq  8(%rdi), %rax
    shlq    $9, %rax
    addq    $-24576, %rax                   # imm = 0xA000
    movsbq  9(%rdi), %rcx
    shlq    $6, %rcx
    addq    $-3072, %rcx                    # imm = 0xF400
    movsbq  10(%rdi), %rdx
    leaq    -384(,%rdx,8), %rdx
    orq %rcx, %rdx
    orq %rax, %rdx
    movsbq  11(%rdi), %rcx
    addq    $-48, %rcx
    orq %rdx, %rcx
    vextracti64x4   $1, %zmm0, %ymm1
    vporq   %zmm1, %zmm0, %zmm0
    vextracti128    $1, %ymm0, %xmm1
    vpor    %xmm1, %xmm0, %xmm0
    vpshufd $238, %xmm0, %xmm1              # xmm1 = xmm0[2,3,2,3]
    vpor    %xmm1, %xmm0, %xmm0
    vmovq   %xmm0, %rax
    orq %rcx, %rax
    vzeroupper
    retq

自定义SIMD实现

inline uint64_t parseOctalToBase10SIMD(const char message[12]) {
    const uint64_t EXTRACTION_MASK_LOW = 0x0707070707070707;
    const uint64_t EXTRACTION_MASK_HIGH = 0x0000000007070707;

    __mmask16 loadMask = 0b0000111111111111;
    __m128i shuffleMask = _mm_setr_epi8(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12);
    __m128i subtrahend = _mm_set1_epi8('0');

    __m128i vector = _mm_maskz_loadu_epi8(loadMask, message);
    vector = _mm_shuffle_epi8(vector, shuffleMask);
    vector = _mm_sub_epi8(vector, subtrahend);

    uint64_t low = _mm_cvtsi128_si64(vector);
    low = _pext_u64(low, EXTRACTION_MASK_LOW);

    vector = _mm_srli_si128(vector, sizeof(uint64_t));
    uint64_t high = _mm_cvtsi128_si64(vector);
    high = _pext_u64(high, EXTRACTION_MASK_HIGH);
    
    return (high << 24) | low;
}

生成的汇编代码

_Z22parseOctalToBase10SIMDPKc:          # @_Z22parseOctalToBase10SIMDPKc
    .cfi_startproc
# %bb.0:
    movw    $4095, %ax                      # imm = 0xFFF
    kmovd   %eax, %k1
    vmovdqu8    (%rdi), %xmm0 {%k1} {z}
    vpshufb .LCPI1_0(%rip), %xmm0, %xmm0    # xmm0 = xmm0[11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12]
    vpaddb  .LCPI1_1(%rip), %xmm0, %xmm0
    vmovq   %xmm0, %rax
    movabsq $506381209866536711, %rcx       # imm = 0x707070707070707
    pextq   %rcx, %rax, %rcx
    vpextrq $1, %xmm0, %rax
    movl    $117901063, %edx                # imm = 0x7070707
    pextq   %rdx, %rax, %rax
    shlq    $24, %rax
    orq %rcx, %rax
    retq

性能测试结果

我生成1亿个随机初始化的八进制字符串,循环调用两种实现处理,10次迭代的平均数据如下:

实现方式平均完成时长(μs)σ
编译器优化版本131836±2488
自定义SIMD版本161733±15987

编译器优化版本性能明显更优,不仅耗时更短,而且标准差远低于自定义实现。


疑问与讨论

我搞不懂为什么编译器优化的版本更快。查了指令微操作数,没发现我的实现有明显高开销的指令,而且我的指令数更少。

写SIMD版本时,我没法把数据一直留在向量寄存器里,只完成了ASCII转整数的向量操作,之后只能转成64位整数用pextq来位压缩——没找到更好的向量内位堆叠方法。另外我用_mm_maskz_loadu_epi8是因为输入内存不保证对齐,而且长度不是16字节,这会不会是性能瓶颈?但我不想给输入加对齐或长度限制。

自定义实现的高标准差让我怀疑是不是缓存问题,但我不确定。希望能得到相关分析和建议。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 00:17:27