寄存器分配相关的Arm性能问题

问题描述 投票:0回答:0

我目前正在使用 cortex A77 cpu 测量以下代码在嵌入式板上的性能。

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;
}

每个输入/输出缓冲区的大小约为 96MB,在运行内核 1000 次后,性能值被测量为移除前 200 次的平均值。 上面代码(kernel_func_x16)的测量结果约为 8.3 ms(使用 O3 选项)。

下面运行代码(kernel_func_x32),性能测得约为 11.1 毫秒。

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;
}

要找出上面性能出来的原因,将每个代码生成为汇编代码。下面的代码是依次加入kernel_func_x16kernel_func_x32的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 ///////
    .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

两个汇编代码的关键循环代码位于.L2标签中。

起初很难找到kernel_func_x32性能下降的原因,所以我通过各种方式修改汇编代码并进行分析。

首先,我重新整理了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

由于上述代码具有类似于 kernel_func_x16 的加载/存储指令结构,并且执行较少的比较指令。 我猜上面的代码性能会与 kernel_func_x16 相似或更快。

但是当运行该代码时,性能测得约为 11 毫秒。

在寻找性能没有提高的原因时,我不小心修改了代码,将 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.2 毫秒。 虽然操作相同,但由于更改了寄存器名称,性能得到了提高。 找了好几个文档找原因,都没有找到。 如果有人能给我一些建议,我将不胜感激。

performance assembly cpu-architecture arm64
© www.soinside.com 2019 - 2024. All rights reserved.