android-ndk-r6を使ってあるソースコードをビルドしました。 ビルド結果のプログラムの実行速度が遅かったため、ビルド対象のソースコードを調べたところ、実行時間の9割を浮動小数点演算が占めていることが判明しました。かつARMは浮動小数点演算が苦手とのことなので、コンパイラオプションを使って手軽に浮動小数点演算の速度の向上を図りました。 Web上を検索すると、APP_CFLAGSに”-msoft-float”オプションを指定するのみでうまくいっているような例が多々ありましたが、同じやり方で速度は改善せず、そもそも最適化されていないように見えました。それはさすがにおかしいので調査をしました。このエントリはそのログです。プログラムはGALAXY Tab上で動作させていました。
解決策からいうと、Application.mkを以下のように記述すればよいことがわかりました。 APP_CFLAGSには最適化オプションを指定する必要はなく、APP_ABIを指定するのみです。 これで、NDKが浮動小数点演算の最適化オプション等を勝手につけてコンパイルしてくれます。 また、Web上にあるAPP_CFLAGSに”-march”オプションや”-msoft-float”オプションをつけてうまくいったという例は、ビルドツールのバージョンがandroid-ndk-r6より古いことや、ARMv7-aのアーキテクチャを対象としていなかったからのようです。
Application.mk
APP_PROJECT_PATH := /home/kurusugawa/workspace/sample
APP_MODULES := sample
APP_ABI := armeabi-v7a以下、調査・解決手順です。 まず、Application.mkを以下のように記述しました。
Application.mk
APP_PROJECT_PATH := /home/kurusugawa/workspace/sample
APP_MODULES := sample
APP_CFLAGS := -march=armv7-a -mtune=cortex-a8 -mthumb-interwork \
-msoft-float -fpic \
-ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -fno-exceptions -fno-rtti \
-D__ARM_ARCH_7__ -D__ARM_ARCH_7A__ -DANDROID -O2 -DNDEBUG -g“-march=armv7-a”オプションでGALAXY Tabのアーキテクチャに特化させ、かつ”-msoft-float”オプションを指定しました。 ですが速度は改善しませんでした。 最適化オプションを指定する前後でバイナリを比較してみたのですが、完全一致しました。 なので、これらの設定がどこかで無視されているものと考えて調査を続行しました。 grep APP_CFLAGS $(ANDROID_NDK_BASE)/build/core を実行すると、以下のファイルが怪しいことがわかりました。
$(ANDROID_NDK_BASE)/build/core/add-application.mk
ifeq ($(APP_OPTIM),debug)
APP_CFLAGS := -O0 -g $(APP_CFLAGS)
else
APP_CFLAGS := -O2 -DNDEBUG -g $(APP_CFLAGS)
endif自分で定義したAPP_CFLAGSにオプションを追加でつけています。 APP_OPTIMがdebugの場合は、自分で定義したAPP_CFLAGSに最適化無効オプションをつけています。 ですが、APP_OPTIMのデフォルトはreleaseで、私の場合はとくに指定しなかったので “-O2 -DNDEBUG -g” がついていました。 “-msoft-float”オプションは消されていないので、ここが問題ではないようです。
次はgrep gcc $(ANDROID_NDK_BASE)/build/core を実行し、 コンパイルが実行されている個所をたどっていくと、以下のファイルに当たりました。
$(ANDROID_NDK_BASE)/build/core/definitions.mk
define ev-build-file
$$(_OBJ): PRIVATE_SRC := $$(_SRC)
$$(_OBJ): PRIVATE_OBJ := $$(_OBJ)
$$(_OBJ): PRIVATE_DEPS := $$(call host-path,$$(_OBJ).d)
$$(_OBJ): PRIVATE_MODULE := $$(LOCAL_MODULE)
$$(_OBJ): PRIVATE_TEXT := "$$(_TEXT)"
$$(_OBJ): PRIVATE_CC := $$(_CC)
$$(_OBJ): PRIVATE_CFLAGS := $$(_FLAGS)
$$(_OBJ): $$(_SRC) $$(LOCAL_MAKEFILE) $$(NDK_APP_APPLICATION_MK)
@mkdir -p $$(dir $$(PRIVATE_OBJ))
@echo "$$(PRIVATE_TEXT) : $$(PRIVATE_MODULE) <= $$(notdir $$(PRIVATE_SRC))";
$(hide) $$(PRIVATE_CC) -MMD -MP -MF $$(PRIVATE_DEPS).org $$(PRIVATE_CFLAGS) $$(call host-path,$$(PRIVATE_SRC)) -o $$(call host-path,$$(PRIVATE_OBJ)) && \
$$(call cmd-convert-deps,$$(PRIVATE_DEPS))
endefコマンドが実行される直前にその内容を表示させたところ(後で気づきましたが、コンパイラオプションに何が指定されているのか表示させたかったら、"ndk-build -n"を実行すればいいようです)、 gccのオプションに"-march=armv5te" やら "-D__ARM_ARCH_5__" など、指定していないアーキテクチャのオプションが指定されていました。 自分で定義した"-march=armv7-a"は上書きされていました。 "-msoft-float"オプションは指定されていました。なんかおかしいです。このオプションを指定したときとしないときとでビルド結果のバイナリに差異がないので。 次は、ARMv7-a用にコンパイルする方法を調べます。これができれば、この"-msoft-float"オプションの威力が発揮されるかもしれません。 grep -R "march=armv5te" $(ANDROID_NDK_BASE)を実行したところ、以下のファイルにたどり着きました。
$(ANDROID_NDK_BASE)/toolchains/arm-linux-androideabi-4.4.3/setup.mk
TARGET_CFLAGS := \
-fpic \
-ffunction-sections \
-funwind-tables \
-fstack-protector \
-D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ \
-D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ \ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
TARGET_CFLAGS += -march=armv7-a \
-mfloat-abi=softfp \
-mfpu=vfp
TARGET_LDFLAGS += -Wl,--fix-cortex-a8
else
TARGET_CFLAGS += -march=armv5te \
-mtune=xscale \
-msoft-float
endifTARGET_ARCH_ABIを"armeabi-v7a"に指定しなかった場合、"-march=armv5te"オプションがついてしまうことがわかりました。 また、ARMv7-Aでは浮動小数点演算のためのオプションは"-msoft-float"ではなく、VFPを使用するための"-mfloat-abi=softfp"等のオプションを指定するようです。
というわけで、結局のところARMv7-Aの最適化のためには、TARGET_ARCH_ABIに"armeabi-v7a"を設定するのみでよいという結論に至りました。 Application.mkではTARGET_ARCH_ABIではなく、APP_ABIに"armeabi-v7a"を設定すればよいです。
ドキュメントは見よう
NDK付属のドキュメントを参照すると、docs/CPU-ARCH-ABIS.htmlに、armeabi-v7aでは以下の拡張があると書いてあり、
- The VFP hardware FPU instructions.ARMv7-a用のコードを生成するには、
APP_ABI := armeabi-v7aを設定するようにと書いていました。 なるほど。
