LoginSignup
4

posted at

updated at

NEONを実際に書いてみる

はじめに

実装例

main.cpp
#include <arm_neon.h>
#include <iostream>

int main(int argc, char** argv)
{
    unsigned char src0[] = {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15};
    unsigned char src1[] = {100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115};
    unsigned char dst [] = {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0};
    uint8x16_t s0 = vld1q_u8(src0);
    uint8x16_t s1 = vld1q_u8(src1);
    uint8x16_t d  = vaddq_u8(s0, s1); // ここで足し算
    vst1q_u8(dst, d);
    for( auto i = 0;i < 16;i++)
    {
        std::cout << i << '\t' << (int)src0[i] << '\t' << (int)src1[i] << '\t' << (int)dst[i] << std::endl;
    }
    return 0;
}
  • 単一のファイルなので横着してコマンドベタ書きします
build(ARmv7)
g++ -mfpu=neon main.cpp
  • Arm v7 では、コンパイラに明示的にNEONを使うように指定する必要があります(-mfpu=neon)
  • Arm v8 では、基本命令セットに入っているので、指定する必要はありません
build(ARmv8)
g++ main.cpp
  • 実行結果
0       0       100     100
1       1       101     102
2       2       102     104
3       3       103     106
4       4       104     108
5       5       105     110
6       6       106     112
7       7       107     114
8       8       108     116
9       9       109     118
10      10      110     120
11      11      111     122
12      12      112     124
13      13      113     126
14      14      114     128
15      15      115     130
  • 2つのベクトルに入っている要素が足されていることが分かります。
  • 実際に加算処理はvaddq_u8の1回しか行われていないのですが、16個のデータが一度に処理されています

解説

include文

#include <arm_neon.h>
  • 基本的な命令はこいつをincludeするだけですべて使えるようになります
  • GCCの場合、だいたい /usr/lib/gcc/aarch64-linux-gnu/<GCCのバージョン>/include/arm_neon.h
  • Raspberry Pi 4の場合は /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h

データ型について

  • コード中、uint8x16_tという型を使っています
    • <データ型>x<レーン数>_tというフォーマットで指定します。
    uint8x16_t s0 = vld1q_u8(src0);
  • 基本的な型は以下の9つです
型名 各要素の型 レーン数
uint8x16_t uint8_t 16
uint16x8_t uint16_t 8
uint32x4_t uint32_t 4
uint64x2_t uint64_t 2
int8x16_t int8_t 16
int16x8_t int16_t 8
int32x4_t int32_t 4
int64x2_t int64_t 2
float16x8_t float16_t 8
float32x4_t float32_t 4
float64x2_t float64_t 2
  • 例えばuint16は16bit幅の符号なし整数なので、一般的にunsigned shortと呼ばれる型です。
    • uint16x8_tは、unsigned short8個分がまとまって処理できます
  • 上の9つの型は情報量として128bit分保持できます。
  • Arm v8 では128bitレジスタを使い、Arm v7 では64bitレジスタ2つをくっつけて128bitレジスタとして振る舞います
  • また、全型は、その半分の64bit幅のデータ型が存在します
型名 各要素の型 レーン数
uint8x8_t uint8_t 8
uint16x4_t uint16_t 4
uint32x2_t uint32_t 2
uint64x1_t uint64_t 1
int8x8_t int8_t 8
int16x4_t int16_t 4
int32x2_t int32_t 2
int64x1_t int64_t 1
float16x4_t float16_t 4
float32x2_t float32_t 2
float64x1_t float64_t 1
  • 注意が必要なのは、Arm v7では、doubleの演算はサポートされていません。そのため、Arm v7(もしくはRaspberry Pi 3 + 32bit Raspbianのような32bit OS on Arm v8)では、float64x2_tfloat64x1_tは利用できません。
  • また、float16x8_tfloat16x4_tNEONFP16拡張であり、Armv7では、別途別フラグを確認する必要があります。(詳しくは後日解説)
  • また、poly8x16_tpoly16x8_tpoly64x2_tという「多項式型」と言うのも存在します。
    • が、有効な利用方法及び原理を筆者が理解していないため、今回のアドベントカレンダーでは全面的に割愛します
    • Githubで検索しても、引っかかるのはGCCのテストコードが何千ファイルと同じものが引っかかるだけで、具体的に利用している人をGithub上で見たことはありません。
    • 多分CRCSHA1などを計算するのに必要っぽいのだけれど、不明です。

組み込みSIMD命令について

  • サンプルコードでは vaddq_u8という命令を使いました。
    uint8x16_t d  = vaddq_u8(s0, s1); // ここで足し算
  • 各命令は接頭辞のvで始まり、v<命令><bit幅>_<データ型>というフォーマットで表されます。
    • 末尾のデータ型は、引数によって変わり、以下の14種類があります
    • 符号なし整数: u8u16u32u64
    • 符号あり整数: s8s16s32s64
    • 浮動小数点型: f16f32f64
    • 多項式型: p8p16p64
  • 今回はadd命令で、128bit分のデータを利用しました。この場合は末尾にqが付きます。
    • 64bit幅分の同様の命令はvadd_u8です
    • 基本的に64bit幅でも128bit幅でも、演算の内容は同じです
    • vaddに対してvaddqの様に64bit/128bit幅でサイズが違うだけの命令の対が多数存在します。
  • 以上の規則、みたいなものを頭の片隅に入れて本アドベントカレンダーを読んで頂ければより理解が深まるのではないかと思います。

ロードとストアについて

  • vld1q_u8がメモリから読んでレジスタに格納するロード命令
  • vst1q_u8がレジスタからメモリに格納するストア命令
    uint8x16_t s0 = vld1q_u8(src0);
    uint8x16_t s1 = vld1q_u8(src1);
    uint8x16_t d  = vaddq_u8(s0, s1); // ここで足し算
    vst1q_u8(dst, d);
  • loadstoreに関しては後日解説します
  • ベクトルの値から直接printするには、一度メモリに格納する必要があるため、そうしています。

おわりに

  • サンプルコードを示してSIMD演算を行いました
  • 明日も手島の担当で、add命令の解説から順番に始めます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4