首页 > 解决方案 > NEON 汇编代码在 Cortex-A72 与 Cortex-A53 上需要更多周期

问题描述

我在 AArch32 模式下的两个 ARMv8 处理器上对 ARMv7 NEON 代码进行基准测试:Cortex-A53 和 Cortex-A72。我正在使用带有 32 位 Raspbian Buster 的 Raspberry Pi 3B 和 Raspberry Pi 4B 板。

我的基准测试方法如下:

uint32_t x[4];
uint32_t t0 = ccnt_read();
for(int i = 0; i < 1000; i++)
    armv7_neon(x);
uint32_t t1 = ccnt_read();
printf("%u\n",(t1-t0)/1000);

其中 armv7_neon 函数由以下指令定义:

.global armv7_neon
.func armv7_neon, armv7_neon
.type armv7_neon, %function
armv7_neon:
    vld1.32 {q0}, [r0]
    vmvn.i32 q0, q0
    vmov.i32 q8, #0x11111111
    vshr.u32 q1, q0, #2
    vshr.u32 q2, q0, #3
    vmov.i32 q9, #0x20202020
    vand q1, q1, q2
    vmov.i32 q10, #0x40404040
    vand q1, q1, q8
    vmov.i32 q11, #0x80808080
    veor q0, q0, q1
    vmov.i32 q12, #0x02020202
    vshl.u32 q1, q0, #5
    vshl.u32 q2, q0, #1
    vmov.i32 q13, #0x04040404
    vand q1, q1, q2
    vmov.i32 q14, #0x08080808
    vand q3, q1, q9
    vshl.u32 q1, q0, #5
    vshl.u32 q2, q0, #4
    veor q0, q0, q3
    vand q1, q1, q2
    vmov.i32 q15, #0x32323232
    vand q1, q1, q10
    vmov.i32 q8, #0x01010101
    veor q0, q0, q1
    vshl.u32 q1, q0, #2
    vshl.u32 q2, q0, #1
    vand q1, q1, q2
    vand q3, q1, q11
    vshr.u32 q1, q0, #2
    vshl.u32 q2, q0, #1
    veor q0, q0, q3
    vand q1, q1, q2
    vand q1, q1, q12
    veor q0, q0, q1
    vshr.u32 q1, q0, #5
    vshl.u32 q2, q0, #1
    vand q1, q1, q2
    vand q3, q1, q13
    vshr.u32 q1, q0, #1
    vshr.u32 q2, q0, #2
    veor q0, q0, q3
    vand q1, q1, q2
    vand q1, q1, q14
    veor q0, q0, q1
    vmvn.i32 q0, q0
    vand q1,  q0, q14
    vand q2,  q0, q15
    vand q3,  q0, q8
    vand q8,  q0, q11
    vand q9,  q0, q10
    vand q10, q0, q13
    vshl.u32 q1,  q1,  #1
    vshl.u32 q2,  q2,  #2
    vshl.u32 q3,  q3,  #5
    vshr.u32 q8,  q8,  #6
    vshr.u32 q9,  q9,  #4
    vshr.u32 q10, q10, #2
    vorr q0, q1, q2
    vorr q1, q3, q8
    vorr q2, q9, q10
    vorr q3, q0, q1
    vorr q0, q3, q2
    vst1.32 {q0}, [r0]
    bx lr
.endfunc

代码使用以下选项简单地编译:

gcc -O3 -mfpu=neon-fp-armv8 -mcpu=cortex-a53
gcc -O3 -mfpu=neon-fp-armv8 -mcpu=cortex-a72

我在 Cortex-A53 和 Cortex-A72 上分别得到 74 和 99 个周期。我看到这篇博文讨论了 Cortex-A72 上针对 tbl 指令的一些性能问题,但我正在运行的代码不包含任何内容。

这个差距从何而来?

标签: assemblyraspberry-piarmarm64neon

解决方案


我比较了 A72 和 A55 的指令周期时序(A53 上没有):

vshlvshr

A72:吞吐量(IPC)1,延迟 3,仅在 F1 管道上执行
A55:吞吐量(IPC)2,延迟 2,在两个管道上执行(虽然受到限制)

这几乎可以确定,因为您的代码中有很多。

您的汇编代码也有一些缺点:

  1. vadd比 . 具有更少的限制和更好的吞吐量/延迟vshl。您应该将 all 替换vshl为立即 1 和vadd. 桶式移位器比 SIMD 上的算术成本更高。
  2. 您不应不必要地重复相同的说明 ( <<5)
  3. 第二个vmvn是不必要的。您可以将以下所有内容vand替换为vbic
  4. 只要不涉及排列,编译器就会生成可接受的机器代码。因此,在这种情况下,我会在 neon 内在函数中编写代码。

#include <arm_neon.h>

void armv7_neon(uint32_t * pData) {
    const uint32x4_t cx11 = vdupq_n_u32(0x11111111);
    const uint32x4_t cx20 = vdupq_n_u32(0x20202020);
    const uint32x4_t cx40 = vdupq_n_u32(0x40404040);
    const uint32x4_t cx80 = vdupq_n_u32(0x80808080);
    const uint32x4_t cx02 = vdupq_n_u32(0x02020202);
    const uint32x4_t cx04 = vdupq_n_u32(0x04040404);
    const uint32x4_t cx08 = vdupq_n_u32(0x08080808);
    const uint32x4_t cx32 = vdupq_n_u32(0x32323232);
    const uint32x4_t cx01 = vdupq_n_u32(0x01010101);

    uint32x4_t temp1, temp2, temp3, temp4, temp5, temp6;
    uint32x4_t in = vld1q_u32(pData);

    in = vmvnq_u32(in);

    temp1 = (in >> 2) & (in >> 3);
    temp1 &= cx11;
    in ^= temp1;

    temp1 = (in << 5) & (in + in);
    temp1 &= cx20;
    temp2 = (in << 5) & (in << 4);
    temp2 &= cx40;
    in ^= temp1;
    in ^= temp2;

    temp1 = (in << 2) & (in + in);
    temp1 &= cx80;
    temp2 = (in >> 2) & (in >> 1);
    temp2 &= cx02;
    in ^= temp1;
    in ^= temp2;

    temp1 = (in >> 5) & (in + in);
    temp1 &= cx04;
    temp2 = (in >> 1) & (in >> 2);
    temp2 &= cx08;
    in ^= temp1;
    in ^= temp2;

    temp1 = vbicq_u32(cx08, in);
    temp2 = vbicq_u32(cx32, in);
    temp3 = vbicq_u32(cx01, in);
    temp4 = vbicq_u32(cx80, in);
    temp5 = vbicq_u32(cx40, in);
    temp6 = vbicq_u32(cx04, in);

    temp1 += temp1;
    temp2 <<= 2;
    temp3 <<= 5;
    temp4 >>= 6;
    temp5 >>= 4;
    temp6 >>= 2;

    temp1 |= temp2 | temp3 | temp4 | temp5 | temp6;

    vst1q_u32(pData, temp1);
}

螺栓链接

您可以看到该-mcpu选项在此处产生了明显的不同。

vbic但是 GCC 永远不会让人失望:即使我明确命令它使用它也拒绝使用(Clang 也是如此。我讨厌它们)

我会进行拆卸,卸下第二个vmvn,然后更换所有vand附件以vbic获得最佳性能。

请记住,用汇编编写代码不会自动使代码运行得更快,而且较新的架构不一定会带来更有利的 ICT:在 ICT 方面,A72 在很大程度上不如 A53。

PS:使用-mcpu=cortex-a53选项生成的代码与 a55 的相同。我们可以假设 A55 只是armv8.2ISA 对 A53 的扩展。


推荐阅读