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

Cortex-A77平台寄存器分配对代码性能的影响咨询

Cortex-A77上字节拷贝代码的性能差异分析

测试环境与代码

在搭载Cortex-A77 CPU的嵌入式板上测试两段字节拷贝代码,输入/输出缓冲区大小约96MB,运行内核1000次并剔除前200次取平均值,开启O3优化。

kernel_func_x16代码

void kernel_func_x16(unsigned char* __restrict input_data, unsigned char* __restrict output_data)
{
    int stride_size=16;

    for(int i=0; i<100000000; i+=stride_size)
    {
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
    }
    return;
}

性能结果:耗时约8.3ms。

kernel_func_x32代码

void kernel_func_x32(unsigned char* __restrict input_data, unsigned char* __restrict output_data)
{
    int stride_size=32;

    for(int i=0; i<100000000; i+=stride_size)
    {
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
        *output_data++ = *input_data++;
    }
    return;
}

性能结果:耗时约11.1ms。

优化后的汇编代码对比

kernel_func_x16汇编(O3优化)

///// KERNEL_FUNC_X16 ///////
    .arch armv8.2-a+crc
    .file   "kernel.cpp"
    .text
    .align  2
    .p2align 4,,11
    .global _Z15kernel_func_x16PhS_
    .type   _Z15kernel_func_x16PhS_, %function
_Z15kernel_func_x16PhS_:
.LFB4340:
    .cfi_startproc
    mov x3, 57600
    mov x2, 0
    movk    x3, 0x5f5, lsl 16
    .p2align 3,,7
.L2:
    ldr q0, [x0, x2]
    str q0, [x1, x2]
    add x2, x2, 16
    cmp x2, x3
    bne .L2
    ret
    .cfi_endproc
.LFE4340:
    .size   _Z15kernel_func_x16PhS_, .-_Z15kernel_func_x16PhS_
    .ident  "GCC: (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0"
    .section    .note.GNU-stack,"",@progbits

kernel_func_x32汇编(O3优化)

///// KERNEL_FUNC_X32 ///////
    .arch armv8.2-a+crc
    .file   "kernel.cpp"
    .text
    .align  2
    .p2align 4,,11
    .global _Z15kernel_func_x32PhS_
    .type   _Z15kernel_func_x32PhS_, %function
_Z15kernel_func_x32PhS_:
.LFB4340:
    .cfi_startproc
    mov x3, 57600
    add x5, x0, 16
    add x4, x1, 16
    mov x2, 0
    movk    x3, 0x5f5, lsl 16
    .p2align 3,,7
.L2:
    ldr q1, [x0, x2]
    ldr q0, [x5, x2]
    str q1, [x1, x2]
    str q0, [x4, x2]
    add x2, x2, 32
    cmp x2, x3
    bne .L2
    ret
    .cfi_endproc
.LFE4340:
    .size   _Z15kernel_func_x32PhS_, .-_Z15kernel_func_x32PhS_
    .ident  "GCC: (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0"
    .section    .note.GNU-stack,"",@progbits

性能调试与关键发现

为缩小性能差距,尝试调整kernel_func_x32的汇编指令序列,使其结构接近kernel_func_x16:

.arch armv8.2-a+crc
    .file   "kernel.cpp"
    .text
    .align  2
    .p2align 4,,11
    .global _Z15kernel_func_x32PhS_
    .type   _Z15kernel_func_x32PhS_, %function
_Z15kernel_func_x32PhS_:
.LFB4340:
    .cfi_startproc
    mov x3, 57600
    add x5, x0, 0
    add x4, x1, 0
    mov x2, 0
    movk    x3, 0x5f5, lsl 16
    .p2align 3,,7
.L2:
    ldr q1, [x0, x2]
    str q1, [x1, x2]
    add x2, x2, 16

    ldr q0, [x5, x2]
    str q0, [x4, x2]
    add x2, x2, 16
    cmp x2, x3
    bne .L2
    ret
    .cfi_endproc
.LFE4340:
    .size   _Z15kernel_func_x32PhS_, .-_Z15kernel_func_x32PhS_
    .ident  "GCC: (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0"
    .section    .note.GNU-stack,"",@progbits

修改后性能仍约为11ms,但仅将汇编中的x5、x4替换为x0、x1后:

.arch armv8.2-a+crc
    .file   "kernel.cpp"
    .text
    .align  2
    .p2align 4,,11
    .global _Z15kernel_func_x32PhS_
    .type   _Z15kernel_func_x32PhS_, %function
_Z15kernel_func_x32PhS_:
.LFB4340:
    .cfi_startproc
    mov x3, 57600
    add x5, x0, 0
    add x4, x1, 0
    mov x2, 0
    movk    x3, 0x5f5, lsl 16
    .p2align 3,,7
.L2:
    ldr q1, [x0, x2]
    str q1, [x1, x2]
    add x2, x2, 16

    ldr q0, [x0, x2]
    str q0, [x1, x2]
    add x2, x2, 16
    cmp x2, x3
    bne .L2
    ret
    .cfi_endproc
.LFE4340:
    .size   _Z15kernel_func_x32PhS_, .-_Z15kernel_func_x32PhS_
    .ident  "GCC: (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0"
    .section    .note.GNU-stack,"",@progbits

性能提升至约8.2ms,与kernel_func_x16基本持平。

差异原因分析建议

从Cortex-A77的微架构特性出发,寄存器替换导致的性能差异可能源于以下几点:

  1. 参数寄存器的硬件通路优化
    Cortex-A77的地址生成单元(AGU)对函数参数寄存器(x0-x7)有专门的优化设计。x0/x1作为函数调用的固定参数寄存器,硬件在地址计算时会优先分配低延迟的通路,能与加载/存储操作更好地并行。而x5/x4这类通用寄存器的地址计算可能需要经过额外的端口,微小的延迟在大循环中被累积放大。

  2. 冗余寄存器的依赖链影响
    虽然add x5, x0, 0是逻辑冗余操作,但硬件指令调度器无法完全消除其依赖关系。循环中ldr q0, [x5, x2]的地址计算需要等待x5的值就绪,即使x5从未改变,硬件仍会维护这条依赖链,限制了指令级并行(ILP)的发挥。直接使用x0时,地址计算可直接复用已就绪的基址,调度效率更高。

  3. 内存预取器的行为差异
    Cortex-A77的硬件预取器依赖基址寄存器识别连续内存流。使用x0作为基址时,预取器能稳定关联到同一内存访问模式,预取效率更高;而使用x5作为基址(即使x5=x0),预取器可能将其视为独立流,导致缓存命中率下降,增加等待时间。

  4. 寄存器重命名的潜在开销
    Cortex-A77的寄存器重命名机制对不同寄存器组有细微差异。x0/x1属于高频使用的参数寄存器,重命名逻辑更高效,能减少流水线气泡(stall);而x5/x4的重命名可能引入额外开销。

验证建议

  • 替换x5/x4为其他参数寄存器(如x2/x3),观察性能是否接近x0/x1版本;
  • 借助硬件性能计数器,对比两种版本的L1缓存命中数、AGU利用率、指令调度延迟等指标;
  • 检查编译器是否对x0/x1的内存访问触发了特定优化(如内联memcpy的专用路径)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 09:32:00