优化NEON XOR实施

时间:2013-10-03 15:21:04

标签: c optimization arm neon cpu-cache

尝试xor一个巨大的uint32数组我决定使用NEON协处理器。

我实施了两个c版本:

版本1:

uint32_t xor_array_ver_1(uint32_t *array, int size)
{
    uint32x2_t acc = vmov_n_u32(0);
    uint32_t acc1 = 0;
    for (; size != 0; size -= 2) {
        uint32x2_t vec;
        vec = vld1_u32(array);
        array += 2;
        acc = veor_u32(acc, vec);
    }
    acc1 = vget_lane_u32(acc,0) ^ vget_lane_u32(acc,1);
    return acc1;
}

第2版:

uint32_t xor_array_ver_2(uint32_t *array, int size)
{
    uint32x4_t acc = vmovq_n_u32(0);
    uint32_t acc1 = 0;

    for (; size != 0; size -= 4) {
        uint32x4_t vec;
        vec = vld1q_u32(array);
        array += 4;
        acc = veorq_u32(acc, vec);
    }

    acc1 ^= vgetq_lane_u32(acc,0);
    acc1 ^= vgetq_lane_u32(acc,1);
    acc1 ^= vgetq_lane_u32(acc,2);
    acc1 ^= vgetq_lane_u32(acc,3);

    return acc1;
}

将上述两个版本与传统的xor实现进行比较:

for (i=0; i<arr_size; i++)
        val ^= my_array[i];

我发现了2个问题:

  1. 版本1具有相同的性能。
  2. 版本2比 30%更好。

    1. 我可以重写它甚至更好吗?其中my_array被声明为 uint32_t my_array[BIG_LENGTH];
    2. 是否有非NEON 方式可以提高常规xoring代码的性能? unrolling the loop没有任何改进。

4 个答案:

答案 0 :(得分:5)

这很可能是内存带宽有限 - 一旦你使可用的DRAM带宽饱和,这对于每个负载只有一个ALU操作应该很容易,你将无法从优化中获得任何进一步的好处。

如果可能的话,尝试将您的XOR与同一数据上的其他操作结合起来 - 这样就可以分摊缓存未命中的成本。

答案 1 :(得分:2)

众所周知,gcc上的霓虹内在函数很糟糕。不确定它是否有所改进,但在asm中执行相同的任务应该会给你带来比普通c更好的改进30%。您可能需要首先展开内部循环。将内在函数转换为适当的asm的简单方法是使用与内在函数一起工作的armcc(arm编译器)。

因此,首先尝试展开您的普通c版本(伪代码):

for (i=arr_size; i<arr_size; i -= 4)
{
    val1 ^= my_array[0];
    val2 ^= my_array[1];
    val1 ^= my_array[2];
    val2 ^= my_array[3];
    my_array += 4;
}
用霓虹灯做类似的事情可以给你更好的结果。最后,你应该切换到neon asm,这很简单(就我个人而言,我发现它比内在函数更容易编写)。

这是NEON asm 建议(未经测试,由您决定如何组装)

//data has to be suitably aligned (it has to be 8 or 16 byte aligned, not sure).
//dataSize in bytes has to be multiple of 64 and has to be at least 128.
//function does xor of uint32_t values and returns the result.
unsigned xor_array_64(const void *data, int dataSize);

xor_array_64:
      vldm r0!,{d0-d7}
      subs r1,r1,#0x40
0:
      pld [r0, #0xC0]
      vldm r0!,{d16-d23}
      veor q0, q0, q8
      veor q1, q1, q9
      veor q2, q2, q10
      veor q3, q3, q11
      subs r1,r1,#0x40
      bge 0b

      veor q0, q0, q1
      veor q2, q2, q3
      veor q0, q0, q2
      veor d0, d0, d1

      vtrn.32 d1, d0
      veor d0, d0, d1

      vmov r0, s0
      bx lr

答案 2 :(得分:2)

没有任何代码段的长篇答案。

硬件限制

首先,您应该问自己,我期待什么?你想写最快的代码吗?你怎么验证?首先,例如,编写一些关于硬件可以实现的测试。正如人们所说,这将主要是内存带宽有限,但是你需要知道你的内存接口有多快。弄清楚你的平台的L1,L2和ram容量/性能特性,然后你就会知道对于不同的缓冲区大小,你最多可以期待什么。

编译器

您使用的是最新的编译器吗?接下来的问题是,您是否正在使用最适合您的工具?除非你这样说,否则大多数编译器都没有积极地尝试优化你的代码。您是否正在配置它们以获得最佳收益?您是否启用完全优化(gcc:-O3),矢量化(gcc:-ftree-vectorize -ftree-vectorizer-verbose = 1)?您是否为平台设置了正确的配置标志(-mcpu -mfpu)?

编译器是否生成verifying object code?对于这样一个简单的循环,这将非常简单,并帮助您尝试许多配置选项并检查生成的代码。

调整菜谱方案

您是否正在检查使用restricted pointers是否可以改善效果?

alignment information怎么样? (例如,您没有在您的内在函数示例中提及,但他们希望大小为2或4的乘法,当然使用四重寄存器可以创造%30的改进。)

还尝试在缓存行大小上进行对齐?

硬件功能

你知道你的硬件有什么用吗?例如,Cortex-A9被引入为“无序推测问题超标量”。你能利用双重问题能力吗?

所以答案介于“它取决于”和“你需要实验”之间。

答案 3 :(得分:1)

我不是为ARM编写的,我根本不熟悉NEON,但我有以下想法,这取决于ARM NEON是一个流水线架构,我不知道是不是。 ...

如果Paul R对您的内存带宽已经饱和是正确的,那么这可能几乎没有任何好处,但是如果您按照以下方式稍微重新构建代码会怎样......

uint32_t xor_array_ver_2(uint32_t *array, int size)
{
  // Caveat:  'size' must be a positive multiple of 4, otherwise this
  //          code will loop for a very long time... and almost certainly
  //          segfault (or whatever term your system uses).

  uint32x4_t acc = vmovq_n_u32(0);
  uint32x4_t next_vec = vld1q_u32(array);
  uint32_t acc1 = 0;

  for (size-=4, array+=4; size != 0; size-=4) {
     uint32x4_t vec = next_vec;
     array += 4;
     next_vec = vld1q_u32(array);
     acc = veorq_u32(acc, vec);
  }
  acc = veorq_u32(acc, next_vec);

  acc1 ^= vgetq_lane_u32(acc,0);
  acc1 ^= vgetq_lane_u32(acc,1);
  acc1 ^= vgetq_lane_u32(acc,2);
  acc1 ^= vgetq_lane_u32(acc,3);

  return acc1;
}

....目标是在下一个循环需要之前开始加载下一个向量元素。

你可能尝试的另一个小麻烦是:

uint32_t xor_array_ver_2(uint32_t *array, int size)
{
  // Caveat:  'size' must be a positive multiple of 4, otherwise this
  //          code will loop for a very long time... and almost certainly
  //          segfault (or whatever term your system uses).

  uint32x4_t acc = vmovq_n_u32(0);
  uint32x4_t next_vec = vld1q_u32(&array[size-4]);
  uint32_t acc1 = 0;

  for (size-=8; size>=0; size-=4) {
     uint32x4_t vec = next_vec;
     next_vec = vld1q_u32(&array[size]);
     acc = veorq_u32(acc, vec);
  }
  acc = veorq_u32(acc, next_vec);

  acc1 ^= vgetq_lane_u32(acc,0);
  acc1 ^= vgetq_lane_u32(acc,1);
  acc1 ^= vgetq_lane_u32(acc,2);
  acc1 ^= vgetq_lane_u32(acc,3);

  return acc1;
}