我正在寻找工具和方法来确定我的Cocoa和Cocoa-Touch程序的哪些部分最有助于最终的二进制图像大小,以及帮助减少它的方法.我不是在寻找一个"魔术子弹"编译器标志.我正在寻找用于评估和减少图像尺寸浪费的剖析技术,与Shark和Instruments帮助进行运行时评估相同.
一阶近似可能是.o的大小,但在优化和死代码剥离后的最终图像大小方面,这是多么值得信赖?如果我将所有的.o加起来,它们比我的最终图像大得多,所以链接器显然已经帮我了很多.但这意味着.o的大小可能不是一个有用的衡量标准.
在不破坏代码可维护性的情况下,其他人会在哪些方面减少图像大小?
Apple在代码大小性能指南方面有一些很棒的文档,几乎所有这些都以某种形式适用于这个问题.如果需要,甚至还有针对迂腐方法的提示,例如在二进制中手动排序符号.:-)
我完全喜欢简单,纤薄的代码并最大限度地减少磁盘/内存占用.过早优化总是一个坏主意,但一致的内务管理可以是防止残余累积的好方法.不幸的是,我不知道一种自动分析代码大小的方法,但是有一些工具可以帮助提供具体的洞察力.
对象文件并不像你猜的那样可怕.总和小于部分的一个原因是因为代码全部用单个头连接在一起.虽然百分比不准确,但最大的目标文件是链接二进制文件的最大部分.
为了理解目标文件中每个特定方法的原始长度,您可以使用/usr/bin/otool
打印出汇编代码,用Objective-C方法名称标点:
$ otool -tV MyClass.o
我寻找对应于相对简短或简单方法的长组装,并检查代码是否可以简化或完全删除.
除此之外otool
,我发现它/usr/bin/size
非常有用,因为它分层次地分解了段和段,并显示了每个段的大小,包括目标文件和编译的二进制文件.例如:
$ size -m -s __TEXT __text MyClass.o $ size -m /Applications/iCal.app/Contents/MacOS/iCal
这是一个"更大的图片"视图,虽然它通常强化通常__TEXT __text
是文件中最大的视图之一,因此是开始修剪的好地方.
没有人真的希望他们的二进制文件充斥着从未使用过的代码.在像Objective-C这样的动态且松散耦合的语言中,静态地确定特定代码是否"被使用"可能是困难的或不可能的.即使实例化类或调用方法,跟踪代码路径(理论和实际)也是一件令人头疼的事.我用一些技巧来帮助解决这个问题.
对于静态分析,我强烈推荐Clang静态分析器(在Snow Leopard上很好地内置到Xcode 3.2中).在其所有其他优点中,该工具可以跟踪代码路径,识别不可能执行的代码块,并且应该被删除或者应该修复周围的代码以便可以调用它.
对于动态分析,我使用gcov
(使用单元测试)来识别实际执行的代码.覆盖率报告(使用类似CoverStory的内容读取)显示未执行的代码,再加上手动检查和测试,可以帮助识别可能已经死亡的代码.您必须调整一些设置并在二进制文件上手动运行gcov.我用这篇博文开始了.
在实践中,死代码是一个足够大的代码比例,以便在二进制大小或加载时间方面产生重大差异,这是不常见的,但是死代码肯定会使维护变得复杂,如果可以的话,最好摆脱它.
降低符号可见性似乎是一个奇怪的建议,但它使事情dyld
(在运行时加载程序的链接器)更容易,并使编译器能够执行更好的优化.考虑隐藏全局变量(未声明为static
)等,方法是在其前面添加"隐藏"属性,或在Xcode中启用"默认隐藏符号"并明确使符号可见.我使用以下宏:
#define HIDDEN __attribute__((visibility("hidden"))) #define VISIBLE __attribute__((visibility("default")))
我发现/usr/bin/nm
用于识别不必要的可见符号以及识别潜在的外部依赖关系是非常宝贵的,您可能不知道或没有考虑过.
$ nm -m -s __TEXT __text MyClass.o # -s displays only a given section $ nm -m -p MyClass.o # -p preserves symbol table ordering (no sort) $ nm -m -u MyClass.o # -u displays only undefined symbols
尽管降低符号可见性不太可能直接减小二进制文件的大小,但编译器可能无法进行改进.此外,您可以减少对您不打算公开的符号的意外依赖性.
除了原始二进制文件大小之外,分析链接到哪些动态库通常非常有用,并且可以消除那些可能不必要的动态库,特别是那些可能尚未加载的不太常用的框架.(你也可以从Xcode看到这个,但是对于复杂的项目,有时事情会漏掉,所以这也可以在建造后进行方便的健全检查.)再次,otool救援......
$ otool -L MyClass.o
另一个(非常详细)替代方案是使用dyld
打印加载的库,如此(从终端):
$ export DYLD_PRINT_LIBRARIES=1 $ /Applications/iCal.app/Contents/MacOS/iCal
这显示了正在加载的内容,包括代码链接的库的依赖关系.
通常,您真正关心的是代码大小和库依赖性是否真正影响启动时间.设置此环境变量将导致dyld
报告负载统计信息,这可以真正帮助确定加载时间的时间:
$ export DYLD_PRINT_STATISTICS=1 $ /Applications/iCal.app/Contents/MacOS/iCal
在Leopard及更高版本中,您会注意到有关" dyld共享缓存 "的条目.基本上,动态链接器创建一个由最常用的动态库组成的整合"超级库".在Apple文档中提到了,并且可以使用DYLD_SHARED_REGION
和DYLD_NO_FIX_PREBINDING
环境变量更改行为,类似于上面的内容.详情man dyld
请见.