Профилирование Objective-C размер двоичного изображения

Я ищу инструменты и подходы к определению того, какие части моих программ Cocoa и Cocoa -Touch больше всего способствуют окончательному размеру бинарного изображения и способам его уменьшения. Я не ищу флаг компилятора "волшебной пули". Я ищу методы профилирования для оценки и уменьшения отходов изображения в том же духе, что и Shark and Instruments для оценки времени выполнения.

В первом приближении может быть размер .o, но насколько это заслуживает доверия с точки зрения окончательного размера изображения после оптимизации и удаления дескриптора? Если я добавлю все .o, они намного больше моего последнего изображения, так что ясно, что компоновщик уже помогает мне значительно. Но это означает, что размер .o не может быть полезной мерой.

Где другие ищут, чтобы уменьшить размер изображения, не подрывая ремонтопригодность кода?

Ответ 1

В 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 Static Analyzer (который удачно встроен в Xcode 3.2 на Snow Leopard). Среди всех других достоинств этот инструмент может отслеживать пути кода, идентифицирующие фрагменты кода, которые невозможно выполнить, и должны быть удалены или окружающий код должен быть исправлен, чтобы он мог быть вызван.
  • Для динамического анализа я использую 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.

Ответ 2

Вы можете посмотреть на otool. В частности, вы, вероятно, захотите использовать флаг -l, который отображает все команды загрузки (a.k.a. разделы и сегменты), которые составляют ваш двоичный файл.

Сказав все это, вы обычно обнаружите, что ресурсы более значительны, чем код, который вы пишете, поэтому мне интересно, какая проблема вы столкнулись с тем, что вы пытаетесь решить. Наши приложения имеют справедливый бит кода, но все еще всего несколько МБ. Возможно, вы статически связываетесь с некоторыми большими библиотеками - я не знаю.

Если большая часть вашего кода Objective-C, очень мало его будет удалено с удалением мертвого кода (по очевидным причинам), так что не будет иметь большого значения.

Что будет иметь значение, так это отладочная информация, которая будет существенной. Ваши объектные файлы будут включать это, но вы, как правило, храните его в отдельном пакете dSYM, когда вы его связываете, поэтому он не будет включен в окончательный двоичный файл (или, по крайней мере, это то, что вы должны делать).

Ваш код будет находиться в сегменте/разделе __TEXT, __text.

Я уверен, что компоновщик объединит эквивалентные строки, поэтому сумма будет меньше суммы частей для этих разделов, но, я думаю, обычно это не так много.

Я также ожидал бы, что ваши разделы перемещения и символов будут меньше, чем сумма частей. Вы должны удалить связанный двоичный код ненужных символов, чтобы сэкономить место (это не то же самое, что удаление информации об отладке). См. Параметр "Связанный с лентой продукт" в Xcode.

Еще одна вещь, которую следует помнить, заключается в том, что ваш связанный двоичный файл будет двоичным файлом FAT, тогда как объектные файлы обычно arent.