【问题标题】:Profiling Objective-C binary image size分析 Objective-C 二进制图像大小
【发布时间】:2010-10-31 22:06:45
【问题描述】:

我正在寻找工具和方法来确定我的 Cocoa 和 Cocoa-Touch 程序的哪些部分对最终二进制图像大小的贡献最大,以及帮助减小它的方法。我不是在寻找“魔术子弹”编译器标志。我正在寻找分析技术来评估和减少图像大小浪费,就像 Shark 和 Instruments 帮助运行时评估一样。

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

其他人希望在不破坏代码可维护性的情况下减小图像大小?

【问题讨论】:

    标签: objective-c cocoa optimization


    【解决方案1】:

    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_REGIONDYLD_NO_FIX_PREBINDING 环境变量来改变行为,类似于上面。详情请见man dyld

    【讨论】:

    • 非常感谢。这正是我正在寻找的东西。我熟悉的所有工具,但没有意识到它们的其他选项。我之前已经研究过符号可见性问题,它肯定会有所不同。那里有很大的危险:如果你抛出一个 .o 并捕获另一个,它可能会搞砸 C++ 异常处理(而且很难检测到这种情况正在发生,而且你不会收到任何警告说事情不起作用)。但对于纯 ObjC 代码,它似乎很有希望。再次感谢。
    • 没问题,我很乐意分享。我认为代码分析应该更普遍,而不是只由代码巫师秘密练习的黑魔法。 ;-) 我不使用 C++,所以我不知道那里的异常问题 - 感谢分享!
    【解决方案2】:

    你可能想看看 otool.具体来说,您可能想要使用 -l 标志来显示构成二进制文件的所有加载命令(也就是节和段)。

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

    如果您的大部分代码都是 Objective-C,那么只有很少一部分代码会通过死代码剥离(原因很明显)被删除,所以这不会有太大的区别。

    真正重要的是调试信息。您的目标文件将包含此文件,但当您链接它时,您通常会将其存储在单独的 dSYM 包中,因此它不会包含在最终的二进制文件中(或者至少这是您应该做的)。

    您的代码将位于 __TEXT、__text 段/部分中。

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

    我还希望您的重定位和符号部分小于部分的总和。您应该剥离不需要符号的链接二进制文件以节省空间(这与剥离调试信息不​​同)。请参阅 Xcode 中的“Strip Linked Product”设置。

    要记住的另一件事是,您的链接二进制文件将是 FAT 二进制文件,而目标文件通常不是。

    【讨论】:

    • 是的,我敢肯定 otool 在某处有秘密......练习的目标与性能调整的目标相同。您不会等到程序开始爬行时才开始询问“我如何在不牺牲可维护性的情况下改进它”。现代二进制文件可能大得惊人,我正在寻找在需要时可以使用哪些优化工具(iPhone 使这更加紧迫,但我们有时在 Mac 上变得草率)。关于胖二进制的好提醒。同意删除死代码,这就是为什么我正在考虑如何发现从未调用过的垃圾。
    猜你喜欢
    • 2011-05-13
    • 2015-10-11
    • 2015-09-19
    • 1970-01-01
    • 1970-01-01
    • 2012-05-07
    • 1970-01-01
    • 2014-01-20
    • 1970-01-01
    相关资源
    最近更新 更多