Apple 在Code Size Performance Guidelines 上有一些很棒的文档,几乎所有这些都以某种形式适用于这个问题。如果需要,甚至还有一些迂腐方法的技巧,比如在二进制文件中手动排序符号。 :-)
我完全喜欢简单、纤细的代码和最大限度地减少磁盘/内存占用。过早的优化总是一个坏主意,但一致的内务管理可能是防止垃圾堆积的好方法。不幸的是,我不知道有一种自动分析代码大小的方法,但是有几种工具可以帮助提供具体的洞察力。
二值图像尺寸
目标文件并不像您想象的那样糟糕。总和小于部分的一个原因是因为代码全部通过一个标头连接在一起。虽然百分比不准确,但最大的目标文件是链接二进制文件的最大部分。
要了解目标文件中每个特定方法的原始长度,您可以使用 /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 Static Analyzer(它很高兴地内置在Snow Leopard 上的Xcode 3.2 中)。除了所有其他优点之外,该工具可以跟踪代码路径并识别不可能执行的代码块,应该删除或修复周围的代码,以便可以被调用。
- 对于动态分析,我使用
gcov(带有单元测试)来识别实际执行了哪些代码。覆盖率报告(用CoverStory 之类的东西阅读)揭示了未执行的代码,再加上手动检查和测试,可以帮助识别可能已死的代码。您确实必须调整一些设置并在二进制文件上手动运行 gcov。我使用 this blog post 开始使用。
在实践中,死代码占代码的比例足够大以对二进制大小或加载时间产生重大影响的情况并不常见,但死代码肯定会使维护复杂化,如果可以,最好将其删除.
符号可见性
降低符号可见性似乎是一个奇怪的建议,但它使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 共享缓存”的条目。基本上,动态链接器创建了一个由最常用的动态库组成的统一的“超级库”。在this Apple documentation 中提到了它,并且可以使用DYLD_SHARED_REGION 和DYLD_NO_FIX_PREBINDING 环境变量来改变行为,类似于上面。详情请见man dyld。