代码之家  ›  专栏  ›  技术社区  ›  Rob Napier

分析Objective-C二进制图像大小

  •  22
  • Rob Napier  · 技术社区  · 17 年前

    我正在寻找工具和方法来确定我的Cocoa和Cocoa Touch程序的哪些部分对最终二值图像大小的影响最大,以及有助于减小二值图像大小的方法。我不是在寻找“魔弹”编译器标志。我正在寻找用于评估和减少图像大小浪费的分析技术,就像Shark和用于运行时评估的仪器一样。

    一阶近似值可能是.o的大小,但就优化和死代码剥离后的最终图像大小而言,这有多可信?如果我把所有的.o加起来,它们比我的最终图像要大得多,所以很明显链接器已经帮了我很大的忙。但这意味着.o的大小可能不是一个有用的衡量标准。

    2 回复  |  直到 17 年前
        1
  •  42
  •   Quinn Taylor    16 年前

    苹果有一些很棒的文档 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 (它很高兴地内置在雪豹的Xcode 3.2中)。在其所有其他优点中,该工具可以跟踪代码路径,识别不可能执行的代码块,并且应该删除或修复周围的代码,以便 可以 被叫来。
    • 对于动态分析,我使用 gcov (使用单元测试)以确定哪些代码是 执行。覆盖率报告(请阅读以下内容 CoverStory this blog post 开始吧。

    在实践中,死代码占代码的比例大到足以在二进制大小或加载时间上产生实质性差异是很少见的,但死代码肯定会使维护复杂化,如果可以的话,最好将其去掉。

    符号可见性

    降低符号可视性似乎是一个奇怪的建议,但这会让您的工作更轻松 dyld static )等等,在它们前面加上“hidden”属性,或者在Xcode中启用“Symbols hidden by Default”,并显式地使符号可见。我使用以下宏:

    #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 -L MyClass.o
    

    另一个(极其冗长)的选择是 戴尔德 打印加载的库,如(从终端):

    $ export DYLD_PRINT_LIBRARIES=1
    $ /Applications/iCal.app/Contents/MacOS/iCal
    

    发射性能分析

    通常,您真正关心的是代码大小和库依赖关系是否真正影响启动时间。设置此环境变量将导致 戴尔德 要报告负载统计信息,这确实有助于确定在负载上花费的时间:

    $ export DYLD_PRINT_STATISTICS=1
    $ /Applications/iCal.app/Contents/MacOS/iCal
    

    dyld共享缓存 this Apple documentation ,并且可以使用 DYLD_SHARED_REGION DYLD_NO_FIX_PREBINDING man dyld 详情请参阅。

        2
  •  3
  •   Chris Suter    17 年前

    你可能想看看耳石。具体来说,您可能希望使用-l标志,该标志显示组成二进制文件的所有加载命令(也称为节和段)。

    话虽如此,您通常会发现资源比您编写的代码更重要,因此我想知道您遇到了什么问题,您正试图解决。我们的应用程序有相当多的代码,但仍然只有几MB。也许你正在静态链接到一些我不知道的大图书馆。

    如果您的大多数代码是Objective-C,那么很少有代码会被死掉的代码剥离(出于明显的原因),所以这不会有太大的区别。

    有区别的是大量的调试信息。您的对象文件将包含此项,但通常在链接时将其存储在单独的dSYM包中,这样它就不会包含在最终的二进制文件中(或者至少这是您应该做的)。

    您的代码将位于_文本、_文本段/节中。

    我很确定链接器将合并等价的字符串,因此总数将小于这些部分的总和,但是,我猜,通常不会太多。

    我还希望你的搬迁和符号部分少于部分的总和。您应该去除链接二进制文件中不需要的符号以节省空间(这与去除调试信息不同)。请参阅Xcode中的“带链接产品”设置。

    要记住的另一件事是,链接的二进制文件将是胖二进制文件,而对象文件通常不是。