3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AARCH64 NEON 非正規化数制御のメモ

Last updated at Posted at 2020-07-22

背景

レイトレーシングではあんまり出てこない気がする非正規化数ですが, embree では非正規化数 off を推奨しているため, ARM NEON の場合どうなるのか気になって調べました.

参考: x85 SSE の場合

以下のように設定できる(default は off = 非正規化数有効).

           #include "xmmintrin.h"
           #include "pmmintrin.h"

           _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); // FTZ
           _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); // DAZ

FTZ: Flash to zero. 演算結果が denormal になったらゼロにする(SSE から)
DAZ: Denormal as zero. 入力が denormal だったらゼロにする(SSE3 から)

NEON の場合

aarch64 アーキテクチャ(arm64v8)を想定します.

FPU 制御レジスタでふるまいを設定できるが...

flush-to-zero: aarch32 NEON では常に有効(レジスタの設定は無視される), aarch64 ではレジスタ制御でいけるっぽい?

denormal as zero は, 明記は無い.

SSE2

# include "xmmintrin.h" // FTZ(SSE)
# include <cstdint>
# include <cstdio>
# include "pmmintrin.h" // DAZ(SSE3)

struct FP32
{
  union {
    float f;
    uint32_t ui;
  };
};

int main(int argc, char **argv)
{
  _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); // FTZ
  _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); // DAZ

  __m128 a = _mm_set1_ps(1.0f / 3.0f);
  __m128 b = _mm_set1_ps(1.17e-38f);

  __m128 c = _mm_mul_ps(a, b);

  float buf[4];

  _mm_storeu_ps(buf, c);

  FP32 fp;
  fp.f = buf[0];

  printf("ret = %g(0x%08x)\n", buf[0], fp.ui);

  return 0;
}

1.17e-38f は 2^(-126) = minimal normal よりちょっと大きな数です.
掛け算したら非正規化数になるようにします.

FTZ, DAZ off : ret = 3.9e-39(0x002a779d)
FTZ on : ret = 0(0x00000000)
DAZ on : re = 0(0x00000000)
FTZ, DAZ on : re = 0(0x00000000)

DAZ を有効にすると, store 命令にも影響があるのか, 今回ではゼロになりました. bit pattern もゼロです.

AARCH64 NEON

# include <cstdint>
# include <cstdio>
# include <cmath>

# include <arm_neon.h>

struct FP32
{
  union {
    float f;
    uint32_t ui;
  };
};

int main(int argc, char **argv)
{
  uint64_t reg = __builtin_aarch64_get_fpcr();
  __builtin_aarch64_set_fpcr(reg | (1ull << 24)); // 24bit: flush-to-zero

  float32x4_t a = vdupq_n_f32(1.0f / 3.0f);
  float32x4_t b = vdupq_n_f32(1.17e-38f);

  float32x4_t c = vmulq_f32(a, b);
  c = vmulq_f32(c, a);

  __attribute__((aligned(16))) float buf[4];

  vst1q_f32(buf, c);

  FP32 fp;
  fp.f = buf[0];

  printf("ret = %g(0x%08x)\n", buf[0], fp.ui);
  printf("FP_SUBNORMAL = %d\n", std::fpclassify(buf[0]) == FP_SUBNORMAL);

  return 0;
}

を参考にして, builtin 命令で浮動小数点数制御レジスタを設定します.
flush-to-zero は 24bit 目になります.

FPCR 制御する builtin 命令は clang は対応していないので, ここでは gcc を使います.
(clang の場合だと assembler で設定になるかしら)

Jetson AGX(aarch64 Ubuntu 18.04 linux)で試しました.

FTZ off

ret = 1.3e-39(0x000e27df)
FP_SUBNORMAL = 1

FTZ on

ret = 0(0x000e27df)
FP_SUBNORMAL = 0

ゼロになりました(FP_SUBNORMAL にも分類されない)が, ビットパターン自体はゼロにはなりませんでした.
また, 非正規化数を入力(load)し, store -> 表示も同様のふるまいでした.

何かしら denormal 自体は計算していて, HW 側で FPCR を見てゼロとみなすかどうかマスクをかけている感があります.

AARCH64 NEON では, flush-to-zero は SSE でいう FTZ + DAZ 両方を対応しているときと同じふるまいと考えてよさそうです.

まとめ

非正規化数は aarch32(32bit) NEON ではサポートされない
aarch64 では非正規化数対応している. denormal as zero だけのモードは無い模様.

SSE で FTZ + DAZ on に設定している場合は, AARCH64 NEON でも flush-to-zero で同様の振る舞いにすることができる.

AARCH64 NEON では, 非正規化数かどうかの判定を, bit pattern で判定するのは避けるのがよさそう.

3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?