mbed で NEON を使ってみる

NEONって使ったことないんですが、GR-PEACHのCPUはARM-CortexA9なので、NEON命令が使えるはずです。mbedのオンラインコンパイラ(ARMCC)では使えるのでしょうか。試してみました。


とりあえず下の配列の合算プログラムをビルド。すんなり通りました。"arm_neon.h"も見えてるし、NEON関数書けばそのまま使えるようです。

合算プログラムは、255までの整数が入ったuint16_tの配列を用意して、その合計を求めます。sum_loop()では単純に配列をループで回して合算し、sum_neon()では、ループでNEON関数をつかって、uint16_t 4列のベクタに配列を読んで、uint32_t 4列のベクタに合算していきます。したがって、ループ回数はsum_loop()の 1/4 になります。最後に、対加算という関数で列同士の合算を行って返しています。

#include <stdio.h>
#include <arm_neon.h>

#include "mbed.h"
Serial pc(USBTX, USBRX);
Timer tim;

void fill_array(uint16_t *array, int size) {
    for (int i = 0; i < size; i++) {
        array[i] = rand() % 255 + 1;
    }
}

int sum_neon(uint16_t *array, int size) {
    uint32x4_t acc1 = vdupq_n_u32(0);
    uint64x2_t ret;
 
    for (; size != 0; size -= 4) {
        uint16x4_t vec = vld1_u16(array);
        array += 4;
        acc1 = vaddw_u16(acc1, vec);
    }
    ret = vpaddlq_u32(acc1);

    return (int)(vgetq_lane_u64(ret, 0) + vgetq_lane_u64(ret, 1));
}

int sum_loop(uint16_t *array, int size) {
    int ret = 0;

    for (int i = 0; i < size; i++) {
        ret += array[i];
    }

    return ret;
}

void test(int size) {
    int s1, s2, e1, e2, sum1, sum2;
    uint16_t my_array[size];

    printf("sum(%d elements) test ----\n", size);

    fill_array(my_array, size);

    s1 = tim.read_us();
    sum1 = sum_neon(my_array, size);
    e1 = tim.read_us();

    s2 = tim.read_us();
    sum2 = sum_loop(my_array, size);
    e2 = tim.read_us();

    printf("  neon:  sum = %d, time = %d [usec]\n", sum1, e1 - s1);
    printf("  loop:  sum = %d, time = %d [usec]\n", sum2, e2 - s2);
    printf("\n");
}

int main() {
    tim.start();

    test(100);

    test(1000);

    test(10000);

    test(100000);

    tim.stop();
    return 0;
}

そして結果は下のようになりました。
サイズ100の配列では単純にループした方が早いけど、それ以外はNEON関数を使ったほうが速かったです。配列のサイズで時間差がまちまちなのは何ででしょう。

sum(100 elements) test ----
  neon:  sum = 13419, time = 6 [usec]
  loop:  sum = 13419, time = 3 [usec]

sum(1000 elements) test ----
  neon:  sum = 128368, time = 3 [usec]
  loop:  sum = 128368, time = 13 [usec]

sum(10000 elements) test ----
  neon:  sum = 1284233, time = 20 [usec]
  loop:  sum = 1284233, time = 119 [usec]

sum(100000 elements) test ----
  neon:  sum = 12859428, time = 470 [usec]
  loop:  sum = 12859428, time = 1595 [usec]

NEON関数を明示的に使ってみましたが、演算結果の桁数まで考慮してNEON関数を使わないといけないので難しい印象です。最適化オプションでNEON命令を使うようにコンパイルできるようなのでそれが良いですね。mbedのオンラインコンパイラでは、コンパイルオプション指定できないので、pragmaで指定できれば良いなと思いました。