前回、誤差逆伝搬法をlibfixmathライブラリを使って固定小数点化した。しかし逆に固定小数点化により性能が低下してしまっている。 やはりライブラリによるオーバヘッドではないだろうかということで、性能解析を行うことにした。
Google PerfによるPerformance解析
まず、MNISTトレーニングプログラムをGoogle Performance Toolsを使って性能要因解析を行った。double版、float版、fix16_t版で比較し、Callee Mapを作成した。
- fix16_t 版
- float版
- double版
fix16_t
版では、ライブラリとして呼び出している、fix16_mul
, fix16_add
が結構な時間を要している。これはどのような実装になっているかというと、ソースコードを読めばわかる。
#ifndef FIXMATH_NO_OVERFLOW fix16_t fix16_add(fix16_t a, fix16_t b) { // Use unsigned integers because overflow with signed integers is // an undefined operation (http://www.airs.com/blog/archives/120). uint32_t _a = a, _b = b; uint32_t sum = _a + _b; // Overflow can only happen if sign of a == sign of b, and then // it causes sign of sum != sign of a. if (!((_a ^ _b) & 0x80000000) && ((_a ^ sum) & 0x80000000)) return fix16_overflow; return sum; }
ここで気が付いたのだが、FIXMATH_NO_OVERFLOW
というマクロが定義されており、このマクロを定義することによってオーバフローを検出しないコードを出力することができる。具体的にはfix16.h
に定義されている以下が使用される。
#ifdef FIXMATH_NO_OVERFLOW static inline fix16_t fix16_add(fix16_t inArg0, fix16_t inArg1) { return (inArg0 + inArg1); } static inline fix16_t fix16_sub(fix16_t inArg0, fix16_t inArg1) { return (inArg0 - inArg1); } #else
コチラを使って、コンパイルして性能測定してみよう。とりあえず詳細な性能測定が面倒なので、time
を使って測定する。
まあまあ、それなりに速くなっているが、それでもdouble, float版には追い付いていない。何故じゃ。
- FIXMATH_NO_OVERFLOW 版
real 0m23.020s user 0m22.412s sys 0m0.544s
- FIXMATH_NO_OVERFLOW 無し版
real 0m48.244s user 0m48.044s sys 0m0.124s
| double 版 | 0m6.659s | | float版 | 0m9.847s | | fix16 w/o NO_OVERFLOW | 0m48.244s | | fix16 w/ NO_OVERFLOW | 0m23.020s |
もともとdouble版をベースに作ったということもあり、floatの方が遅いというのは???だが、fix16_t版は足元にも及んでいない。
Raspberry-Pi3で実行
全く関係ないがRaspberry-Pi3でMNISTプログラムをコンパイルして実行した。全く問題なくlibfixmath, train_twolayrnetもコンパイルできた。実行結果も想定通りだ。
RISC-Vで実行 (コンパイル)
TARGETをRISC-Vに変更し、まずはコンパイルしてみた。
diff --git a/libfixmath/Makefile b/libfixmath/Makefile index b284590..2acfc04 100644 --- a/libfixmath/Makefile +++ b/libfixmath/Makefile @@ -3,15 +3,18 @@ PROJECT = libfixmath LIB = SRC = . INC = +# DEF = -DFIXMATH_NO_OVERFLOW #Compiler settings -CPP = gcc -CC = gcc -AS = gcc -LD = gcc -AR = ar -CPP_FLAGS = -O2 $(INC) -Wall -Wextra -c -CC_FLAGS = -O2 $(INC) -Wall -Wextra -c +TARGET = + +CPP = $(TARGET)gcc +CC = $(TARGET)gcc +AS = $(TARGET)gcc +LD = $(TARGET)gcc +AR = $(TARGET)ar +CPP_FLAGS = -O2 $(INC) $(DEF) -Wall -Wextra -c +CC_FLAGS = -O2 $(INC) $(DEF) -Wall -Wextra -c
$ make TARGET=riscv64-unknown-elf- riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fix16_str.o fix16_str.c riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fract32.o fract32.c riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o uint32.o uint32.c riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fix16_exp.o fix16_exp.c riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fix16_sqrt.o fix16_sqrt.c riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fix16_trig.o fix16_trig.c fix16_trig.c: In function 'fix16_tan': fix16_trig.c:116:9: warning: implicit declaration of function 'fix16_sdiv' [-Wimplicit-function-declaration] return fix16_sdiv(fix16_sin(inAngle), fix16_cos(inAngle)); ^~~~~~~~~~ riscv64-unknown-elf-gcc -O2 -DFIXMATH_NO_OVERFLOW -Wall -Wextra -c -o fix16.o fix16.c riscv64-unknown-elf-ar rcs libfixmath.a ./fix16_str.o ./fract32.o ./uint32.o ./fix16_exp.o ./fix16_sqrt.o ./fix16_trig.o ./fix16.o
train_twolayernetの方も、CC=riscv64-unknown-elf
を設定してコンパイルする。
$ make CC=riscv64-unknown-elf-gcc train_twolayernet_fix16 riscv64-unknown-elf-gcc -std=gnu11 -O3 -g -DFIXMATH_NO_OVERFLOW train_twolayernet_fix16.c -o train_twolayernet_fix16 -L./libfixmath/libfixmath -I./libfixmath/libfixmath -lfixmath -lm