Профилирование Objective-C двоичного размера изображения - PullRequest
22 голосов
/ 07 июня 2009

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

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

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

Ответы [ 2 ]

41 голосов
/ 18 июня 2009

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 shared cache ". По сути, динамический компоновщик создает консолидированную «супер библиотеку», состоящую из наиболее часто используемых динамических библиотек. Это упомянуто в этой документации Apple , и поведение может быть изменено с помощью переменных окружения DYLD_SHARED_REGION и DYLD_NO_FIX_PREBINDING, аналогично приведенному выше. Подробнее см. man dyld.

3 голосов
/ 07 июня 2009

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

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...