当我们刚开始做Algolia的android开发时,二进制文件的大小并不是我主要关注的。事实上我们一开始用的是java,后来出于性能的压迫下才换成了C/C++ 后来要在AVelov(一个android应用)中集成我们的库时,才发现这货太大了:850KB,而AVelov整个app才638k!这就意味着AVelov要翻倍的趋势啊 后来我们将Algolia从850KB减小到了307KB,下面就来分享一下我们所做的东西吧
实际上在我们的底层库中我们没有使用异常,鉴于论述完整性,我也把exceptions一起说了 C++ exceptions 和RTTI默认是关闭的,可以通过设置在Application中设置APP_CPPFLAGS可以打开它,赠送一个使用共享的STL:
APP_CPPFLAGS += -fexceptions -frtti APP_STL := stlport_shared同时使用exceptions和RTTI可能,会显著增加你的binary size,这货能删就删吧,不要手软。还有另外一个避免使用C++exceptions的原因:C++对异常的支持不够好。例如:在jni层根本不可能catch一个C++exceptions然后再启动一个java异常。下面的code将会crash(将来的NDK toolchain可能会fix,说这话是2013/1/10):
try { ... }catch () { env->ThrowNew(env->FindClass("java/lang/Exception"), "Error occured"); }当我们接到Cyril的反馈,开始想办法减小library size时,我们发现最后一次提交后我们的库直接从850KB涨到了1.35M(此处有大汗!)。首先我们就怀疑是NDK toolchain更新导致的,随之用两个版本就测了一把,发现变化微乎其微。 当我们用二分法回溯commit历史时,逮住了罪魁祸首:std::cerr << .... << std::endl; 只是这一行就引进了C++的iostream,经我们测试发现如果用了iostream至少会增加300KB。所以用__android_log_print替换吧:
#include <android/log.h> #define APPNAME "MyApp" __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "The value of 1 + 1 is %d", 1+1);tips: 需要在Android.mk中加上:LOCAL_LDLIBS := -llog
一个非常有效的减小library是使用gcc的visibility feature。这个特性可以让你控制导出在符号表中的函数。jni自带了一个JNIEXPORT的可以标志公开函数的宏。所以需要确认所有的需要导出的函数都有JNIEXPORT:
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)也可以自定义JNIEXPORT:#define JNIEXPORT __attribute__ ((visibility ("default"))) 然后需要在Android.mk中加入:
LOCAL_CPPFLAGS += -fvisibility=hidden LOCAL_CFLAGS += -fvisibility=hidden用了这一招之后library已经减小到809KB(-5%),可能根据project的不同略有差别。
这招可以彻底的缩减library的size,直接砍掉了所有没有用途的函数。怎么使用呢,在Android.mk修改C和C++编译选项,请看:
LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections LOCAL_CFLAGS += -ffunction-sections -fdata-sections LOCAL_LDFLAGS += -Wl,--gc-sections这个优化选项只减少了1%(此处又有汗Σ( ° △ °|||)︴) but如果以上选项贴加在一起,这下就超级牛X了:
LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections -fvisibility=hidden LOCAL_CFLAGS += -ffunction-sections -fdata-sections -fvisibility=hidden LOCAL_LDFLAGS += -Wl,--gc-sections直接减少到了691KB(-18.7%)
用-icf=safe链接选项就可以。但是要注意这招有副作用:有可能移除了内联函数,从而影响程序的性能。 在mips架构上没有这个指令,需要在Android.mk上加判断:
ifneq ($(TARGET_ARCH), mips) LOCAL_LDFLAGS += -Wl,--gc-sections else LOCAL_LDFLAGS += -Wl,--gc-sections,--icf=safe endif这一步减少了0.8%,所有目前操作共减小到了687KB(-19.2%)
如果你想缩减的更彻底,那就需要修改默认编译选项了。这些编译选项因架构而异,例如: 在arm架构上inline-limit设置到64,x86/mips就是300;arm优化flags设置到-Os(size最优),x86/mips上则要设置为-O2(性能最优)。 由于arm事多力广,我们直接使用了arm配置。下面就是在android toolchain(version r8d)我们使用配置选项:
--- android-ndk-r8d/toolchains/mipsel-linux-android-4.6/setup.mk +++ android-ndk-r8d.new/toolchains/mipsel-linux-android-4.6/setup.mk @@ -41,12 +41,12 @@ TARGET_C_INCLUDES := $(SYSROOT)/usr/include -TARGET_mips_release_CFLAGS := -O2 +TARGET_mips_release_CFLAGS := -Os -g -DNDEBUG -fomit-frame-pointer -funswitch-loops - -finline-limit=300 + -finline-limit=64 TARGET_mips_debug_CFLAGS := -O0 -g --- android-ndk-r8d/toolchains/x86-4.6/setup.mk +++ android-ndk-r8d.new/toolchains/x86-4.6/setup.mk @@ -39,13 +39,13 @@ TARGET_CFLAGS += -fstack-protector -TARGET_x86_release_CFLAGS := -O2 +TARGET_x86_release_CFLAGS := -Os -g -DNDEBUG -fomit-frame-pointer -fstrict-aliasing -funswitch-loops - -finline-limit=300 + -finline-limit=64 # When building for debug, compile everything as x86. TARGET_x86_debug_CFLAGS := $(TARGET_x86_release_CFLAGS)这次使用这些新的flags又缩减了8.5%,和之前的累加在一起减少到了613KB(-27.9%)
我们的最终建议是减少支持的架构。如果你有大量的浮点计算出于性能考虑就需要支持armeabi-v7a,但是如果你不需要一个FPU,armeabi也会给出一个近似的结果。然而对于mips处理器...现在市场上还没有用武之地呢~ 如果binary size真的对你很重要,你可以只支持armeabi和x86架构(Application.mk):APP_ABI := armeabi x86 看到了没?砍掉了两个架构,我们的library一下变成了307KB,获得了64%的缩减,这还没有算上iostream增大的1.35M,O(∩_∩)O哈哈~
希望这篇短文能够帮助你缩减android native libraries,毕竟android ndk的默认选项优化的太差了。但是也不要期望和我一样的减幅,这些操作都是因机而异,因code而异的。如果你有其他的缩减binary方法,在评论中分享出来吧!
使用clang编译native code,使用O3或者Oz选项基本上能一步到位,不能不说clang太牛X了,和gcc根本不在一个量级!
转载于:https://www.cnblogs.com/octave/p/4454205.html
