c++程序減肥

本文分享了将Algolia的Android原生库体积从850KB减少至307KB的方法,包括禁用异常处理、避免使用iostream、使用-fvisibility=hidden、删除未使用的函数、减少重复代码及限制支持的架构。

When we started Algolia Development for Android, binary size optimization was not one of our main concerns. In fact we even started to develop in JAVA before switching to C/C++ for reasons of performance.

We were reminded of the importance of binary size by Cyril Mottier who informed us that it would be difficult to integrate our lib inAVelov Android Application because its size. AVelov is 638KB and Algolia was 850KB, which would mean that AVelov would more than double in size with Algolia Search embedded.

To address this problem we managed to reduce Algolia binary size from 850KB to 307KB. In this post we share how we did it.

Do not use Exceptions and RTTI

We actually do not use exceptions in our native lib, but for the sake of completeness, I’ll cover this point too.

C++ exceptions and RTTI are disabled by default but you can enable them via APP_CPPFLAGS in yourApplication.mk file and use a compatible STL, for example:

Whilst using exceptions and RTTI can help you to use existing code, it will obviously increase your binary size. If you have a way to remove them, go for it! Actually, there’s another reason to avoid using C++ exceptions: their support is still far from perfect. For example if was impossible for us to catch a C++ exception and launch a Java exception in JNI. The following code results in a crash (will probably be fixed in a future release of the Android NDK toolchain):

Do not use iostream

When starting to investigate our library size following Cyril’s feedback, we discovered that Algolia binaries had vastly increased in size since our last release (from 850KB to 1.35MB)! We first suspected the Android NDK toolchain since we upgraded it and tested different toolchains, but we only observed minor changes.

By dichotomy search in our commits, we discovered that a single line of code was responsible for the inflation:

As incredible as it may sound, using iostream increases a lot the binary size. Our tests shown that it adds a least 300KB per architecture! You must be very careful with iostream and prefer to use __android_log_print method:

Make sure you also link against the logging library, in your Android.mk file:

Use -fvisibility=hidden

An efficient way to reduce binary size is to use the visibility feature of gcc. This feature lets you control which functions will be exported in the symbols table. Hopefully, JNI comes with a JNIEXPORT macro that flags JNI functions as public. You just have to check that all functions used by JNI are prefixed by JNIEXPORT, like this one:

Then you have just to add -fvisibility=hidden for C and C++ files in Android.mk file:

In our case the binaries were down to 809KB (-5%) but remember the gains may be very different for your project. Make your own measures!

Discard Unused Functions with gc-sections

Another interesting approach is to remove unused code in the binary. It can drastically reduce its size if for example part of your code is only used for tests.
To enable this feature, you just have to change the C and C++ compilation flags and the linker flags in Android.mk:

Of course you can combine this feature with the visibility one:


This optim only got us a 1% gain, but once combined with the previous visibility one, we were down to 691KB (-18.7%).

Remove Duplicated Code

You can remove duplicated code with the –icf=safe option of the linker. Be careful, this option will probably remove your code inlining, you must check that this flag does not impact performance.

This option is not yet available on the mips architecture so you need to add an architecture check inAndroid.mk:

And if you want to combine this option with gc-sections:

We actually only obtained a 0.8% gain in size with this one. All previous optimizations combined, we were now at 687KB (-19.2%).

Change the Default Flags of the Toolchain

If you want to go even further, you can change the default compilation flags of the toolchain. Flags are not identical accross architectures, for example:

  • inline-limit is set to 64 for arm and set to 300 for x86 and mips
  • Optimization flag is set to -Os (optimize for size) for arm and set to -O2 (optimize for performance) for x86 and mips

As arm is used by the large majority of devices, we have applied arm settings for other architectures. Here is the patch we applied on the toolchain (version r8d):

We were good for a 8.5% gain with these new flags. Once combined with previous optimizations, we were now at 613KB (-27.9%).

Limit the Number of Architectures

Our final suggestion is to limit the number of architectures. Supporting armeabi-v7a is mandory for performance if you have a lot of floating point computation, but armeabi will provide a similar result if you do not need a FPU. As for mips processors… well they just are not in use on the market today.

And if binary size is really important to you, you can just limit your support to armeabi and x86 architectures in Application.mk:

Obviously, this optim was the killer one. Dropping two out of four architectures halved the binaries size. Overall we obtained a size of 307KB, a 64% gain from the initial 850KB (not counting the bump at 1.35MB due to iostream).

Conclusion

I hope this post will help you to reduce the size of your native libraries on Android since default flags are far from optimal. Don’t expect to obtain the same size reductions, they will highly depend on your specific usage. And if you know other methods to reduce binary size, please share in the comments!


对于设计嵌入式Linux系统的研发人员来说,有一个问题是必须要考虑到的,那就是存储器的空间。 
我们知道嵌入式Linux系统所用的存储器不是软磁盘、硬盘、ZIP盘、CD-ROMDVD这些众所周知的大容量常规存储器,它使用的是例如Rom, CompactFlashM-SystemsDiskOnChipSONYMemoryStickIBM MicroDrive等体积极小,与主板上的BIOS大小相近,存储容量很小的存储器。所以怎样尽可能的节省空间就显的很重要。 
    
嵌入式系统的存储器中放置的无非是内核,文件系统,软件,以及自己开发的程序。本文就从程序入手,以一个非常简单的C程序来作为例子,通过三步来让它减肥。 
Hello.c: 
#include 
int main () 

   printf ("hello,world"); 
   return 0; 

我们先用正常的编译方法来编译,看看生成的程序的大小是多少 
#gcc –o hello hello.c 
#ls –l hello 
-rwxr-xr-x 1 root root 11542 Nov 13 20:07 hello 
从结果可以看到正常编译后的程序大小是11542Byte, 现在开始我们的三步减肥,看看到底效果如何。 
步骤一:用gcc 的代码优化参数 
    
代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。GCC提供的代码优化功能非常强大, 它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的GCC来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到23 
    
编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完 成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越 快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。 
#gcc –O2 –o hello hello.c 
#ls –l hello 
-rwxr-xr-x 1 root root 11534 Nov 13 20:09 hello 
    
优化过的程序的大小是11534Byte,比正常编译的结果11542Byte似乎没有小多少,不过不用着急,这才是第一步。我们接着往下进行。 
步骤二:用strip 命令 
    
我们知道二进制的程序中包含了大量的符号信息(symbol table),有一部分是用来为gdb除错提供必要帮助的。可以通过readelf –S查看到这些符号信息。 
#readelf -S hello 
Section Headers: 
[Nr] Name Type 
[ 0] NULL 
[ 1] .interp PROGBITS 
[ 2] .note.ABI-tag NOTE 
[ 3] .hash HASH 
[ 4] .dynsym DYNSYM 
[ 5] .dynstr STRTAB 
[ 6] .gnu.version VERSYM 
[ 7] .gnu.version_r VERNEED 
[ 8] .rel.dyn REL 
[ 9] .rel.plt REL 
[10] .init PROGBITS 
[11] .plt PROGBITS 
[12] .text PROGBITS 
[13] .fini PROGBITS 
[14] .rodata PROGBITS 
[15] .eh_frame PROGBITS 
[16] .data PROGBITS 
[17] .dynamic DYNAMIC 
[18] .ctors PROGBITS 
[19] .dtors PROGBITS 
[20] .jcr PROGBITS 
[21] .got PROGBITS 
[22] .bss NOBITS 
[23] .comment PROGBITS 
[24] .debug_aranges PROGBITS 
[25] .debug_pubnames PROGBITS 
[26] .debug_info PROGBITS 
[27] .debug_abbrev PROGBITS 
[28] .debug_line PROGBITS 
[29] .debug_frame PROGBITS 
[30] .debug_str PROGBITS 
[31] .shstrtab STRTAB 
[32] .symtab SYMTAB 
[33] .strtab STRTAB 
    
类似于.debug_xxxx的就是用来gdb除错的。去掉它们不但不会影响程序的执行还可以减小程序的size。这里我们通过strip命令拿掉它们。 
#strip hello 
#ls –l hello 
-rwxr-xr-x 1 root root 2776 Nov 13 20:11 hello 
程序立刻变成2776Byte了,效果不错吧。让我们再接再厉,进行最后一步。 
步骤三:用objcopy 命令 
    
上一步的strip命令只能拿掉一般symbol table,有些信息还是没拿掉,而这些信息对于程序的最终执行是没有什幺影响的。如:.comment; .note.ABI-tag; .gnu.version就是完全可以去掉的。所以说程序还有简化的余地,我们可以使用objcopy命令把它们抽取掉。 
#objcopy –R .comment –R .note.ABI-tag –R .gnu.version hello hello1 
#ls –l hello1 
-rwxr-xr-x 1 root root 2316 Nov 13 20:23 hello1 
    
到这一步,程序的减肥就完成了,我们可以看到程序由正常编译的11542Byte 一下子渐少到2316Byte,效果非常明显。 
小结 
    
程序容量的减小无疑对嵌入式Linux系统的设计有着重要的意义,它为我们节省了大量空间,使得我们可以利用这部分空间来完善我们的系统,比如加大内核等等,毕竟这才是我们最终的目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值