背景
レイトレーシングではあんまり出てこない気がする非正規化数ですが, 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 で判定するのは避けるのがよさそう.