Что говорит Google
Вот какие полезные статьи по теме минимизации размеров бинарных файлов мне удалось найти.
- Как уменьшить размер плагина (на примере Microsoft VC++ 6.0) - давняя статья про оптимизацию размеров плагина для FAR Manager. Методы оптимизации: выравнивание, отключение инициализации CRT, замена strlen на lstrlen (см. дополнение).
- Анатомия C Run-Time, или Как сделать программу немного меньшего размера - хорошая статья на rsdn. Методы оптимизации: отключение инициализации CRT, грамотное использование директивы #import и вычислений с плавающей точкой, использование Automation API для преобразования типов. Рассмотрен вопрос зависимости STL от CRT (std::string - зависит). Статья 2002 года.
- Reduce EXE and DLL Size with LIBCTINY.LIB - экзотический подход - замена LIBC.LIB и LIBCMT.LIB библиотекой LIBCTINY.LIB. Подходит лишь для очень простых приложений, минимально использующих CRT. Статья 2001 года.
- Techniques for reducing Executable size - здесь все методики разложены по полочкам. Рассмотрены ключи компилятора и линкера, приведен список стандартных функций, имеющих Win32-эквиваленты. Статья 2008 года.
- Creating the smallest possible PE executable - здесь описывается, как получить рабочий exe-файл размером всего 133 байта.
- Создание компактных приложений на VC++ - Статья на хабре. Рассматривается вариант использования стандартной библиотеки "msvcrt.dll" вместо актуальных "msvcr71.dll" и "msvcp71.dll". Из Windows Driver Kit (WDK) нужно взять файлы, содержащие разницу между актуальной и стандартной версией runtime-библиотек и прилинковать эти файлы к приложению. Из минусов - придется ограничить минимальную версию операционной системы, например Windows XP). Статья 2010 года.
- Dynamically linking with MSVCRT.DLL using Visual C++ 2005 - а это оригинальная статья, в которой предложен трюк с WDK-файлами. 2007 год.
Что можно сделать
Большинство методик, изложенных выше, подходят для очень небольших приложений. Таких, которые используют минимум или вообще не используют возможности стандартной библиотеки C++. В моем приложении функции CRT напрямую не используются. Зато плагин очень интенсивно использует библиотеки STL, Boost и stlsoft. Поэтому отказаться от CRT или заменить ее альтернативным вариантом вряд ли получится. Хотя попробовать конечно стоит. Это - первое направление.
Второе направление работ - замена функций аналогами. Windows и FAR предоставляют ряд функций-аналогов, которые могут запросто заменить многие функции библиотеки С++. Если вызов библиотечных функций увеличивают размеры итоговой dll, то вызовы аналогов практически ничего не будут стоить.
Третье направление - борьба с разбуханием шаблонов. Как известно, опрометчивое использование шаблонов быстро приводит к разбуханию бинарного файла. Если у меня в коде используется два контейнера -
std::vector<int>
и std::vector<byte>
, то это два разных класса, так что код вектора будет сгенерирован дважды. В моем приложении используется приличное количество контейнеров и, вполне возможно, от некоторых можно отказаться. Естественно, такие замены должны быть прозрачными, легковесными и не требовать внесения больших изменений в код.Четвертое направление - попробовать поварьировать настройки компилятора и используемые инструменты. Проверить, что будет, если заменить VC2008 на VC2010, стандартную STL на STLPort и т.п.
Результаты оптимизации
Оптимизация была проведена в 7 шагов. На каждом шаге плагин собирался на Visual Studio 2008 и Visual Studio 2010 для платформ x86 и x64. Результаты собраны в таблицу.
Размер в kb x86 x64 VC2008 VC2010 VC2008 VC2010 0 (static, /MT) 682 640 1067,5 869,5 0 (dll, /MD) 469 440,5 742 575,5 Шаг 1.-std::locate 526 473 816 616,5 Шаг 2. 2х map -> list. 519,5 466,5 806,5 610 Шаг 3. map -> list. 517,5 464,5 803 606,5 Шаг 4. -vector; 517 463,5 801 605 Шаг 5. /Os 413,5 361,5 718 543 Шаг 6. -vector. 411 361 713,5 542 Шаг 7. -regex 389 348 679,5 522,5
Шаг 0 - это исходная позиция. Для удобства приведены размеры DLL с двумя вариантами линковки библиотек С++ - статическим (/MT) и динамическим (/MD). Шаг 1 Обнаружил в коде функцию, преобразующую строку к нижнему регистру. Функция была реализована таким образом:
boost::algorithm::to_lower(dest, std::locale(""))
. Far Manager предоставляет функцию-аналог для приведения символа к нижнему регистру - FarStandardFunctions.LLower(ch)
. Заменил аналогом. Выигрыш оказался существенным ~150-250 kb. Подозреваю, что прежде всего за счет исключения std::locale.Шаг 2 В коде широко используются контейнеры vector и list. А вот map используется всего три раза. Причем функциональность map там как таковая не нужна - в контейнеры помещается по 5-7 элементов. Заменил
std::map<std::wstring, int> std::map<std::wstring, std::wstring> на vector<std::pair<std::wstring, std::wstring> >
(такой вектор в программе уже использовался). Выиграл ~10 kb.Шаг 3 Заменил и последний мап
std::map<pointer, pointer>
на новый list, которого в программе еще не было. Выиграл ~3 kb. Шаг 4 Исключил
std::vector<int>
, заменил уже имеющимся std::vector<byte>
. Сэкономил ~ 1 kb.Шаг 5 Включил опцию компилятора /Os (Favor small code). Ранее была включена опция /Ot. Размер уменьшился на ~100 kb.
Шаг 6 Исключил вектор
std::vector< std::pair<tstring> >
, заменил имеющимся аналогичным списком. Экономия ~3 kb.Шаг 7 Приложение интенсивно использует boost::regex. Обнаружил, что у меня используется два варианта функций - для
std::wstring
и для wchar_t const*
. Оставил только стринги. DLL похудела еще на ~25 kb.Результат неплохой - размер DLL уменьшился примерно в 2 раза и стал даже меньше варианта "0 (dll)". Наибольший эффект, конечно, дали исключение из кода вызова std::locale и включение опции компилятора /Os, но и чистка шаблонов тоже не прошла незамеченной. Очень порадовала Visual Studio 2010 - размер компилируемых файлов существенно ниже.
boost::regex vs boost::xpressive
Как известно, с недавнего времени в boost имеется две библиотеки для работы с регулярными выражениями: boost::regex и boost::xpressive. Последняя состоит целиком из заголовочных файлов (не требует статической линковки с какими-либо lib-файлами) и позволяет задавать регулярные выражения не только динамически (в строковом виде), но и статически, на уровне классов. По заверениям разработчиков, статические выражения дают прирост производительности на уровне 15%. При этом boost::xpressive поддерживает тот же интерфейс, что и boost::regex, так что заменить одну библиотеку на другую - не проблема. Заменил:
Размер в kb x86 x64 VC2008 VC2010 VC2008 VC2010 Шаг 7 389 348 679,5 Шаг 8. Замена regex на xpressive 473,5 457,5 779 638,5
К сожалению, стало хуже. С точки зрения размеров генерируемого кода boost::regex в данном случае оказался несколько более оптимальным. Оставил boost::regex.Visual C++ STL vs STLPort
Попробовал заменить родную STL на STLport-5.2.1. По идее, чтобы использовать stlport, необходимо пересобирать boost. Все из-за boost::regex, т.к. она использует lib-файлы, которые требуется прилинковывать к приложению. Именно их необходимо пересобрать с использованием stlport. Чтобы избежать этой мороки (мне ведь нужно всего лишь попробовать), я заменил boost::regex на boost::xpressive и собрал приложение с stlport без пересборки boost. Собрал только на VC2008 под x86.
Размер в kb x86 x64 VC2008 VC2010 VC2008 VC2010 Шаг 8 473,5 457,5 779 638,5 Шаг 9. Замена STL на STLPort 545 - - -
Результаты огорчили. Размер приложения вырос довольно ощутимо. Будем ждать выхода новой версии stlport.Замена CRT
Можно ли заменить CRT на меньшую? Вариантов замены как минимум три: Tiny C Runtime Library (доработанная LIBCTINY.LIB), Win32API CRT и ntdll.dll.
Честно говоря, я не стал особо заморачиваться с такой заменой. Это не тот случай, когда такая замена может быть оправдана. Гораздо больше меня заинтересовала методика линковки с msvcrt.dll (см. последние две статьи в списке статей выше). Почему бы не попробовать?
Попробовал. И воткнулся в ряд проблем. Во-первых, если ваше приложение прилинковывает какие-либо сторонние библиотеки, то их нужно ПЕРЕсобрать с использованием msvcrt.dll. В моем случае, такой пересборки потребовала библиотека boost::regex.
Во-вторых, невозможно использовать стандартную STL, т.к. она полагается на стандартную CRT. Поподробнее об этом написано вот здесь. Так что нужно использовать STLPort.
Но STLPort тоже требует сборки, если используются iostream. В моем приложении, к сожалению, стримы где-то (неявно) зацеплены. В результате, STLPort так же требуется собирать с msvcrt.dll.
В итоге, чтобы прилинковаться к msvcrt.dll потребуется по-хитрому пересобрать stlport и boost. Овчинка выделки не стоит. Особенно если учесть, что stlport в настоящее время дает существенно большие размеры dll, чем стандартная STL.
P.s. Но в принципе такая работа похоже выполнима. По крайней мере, вот в этом проекте подобным образом пересобрали, например, OpenSSL.
Новшества C++ 1X
Теоретически, для уменьшения размера приложения можно попробовать применить новые возможности компилятора Visual C++ 2010. Прежде всего, rvalue reference и класс
unique_ptr
в качестве замены boost::shared_ptr
. Однако все это требует существенного изменения кода приложения, так что оставлю это на будущее.Выводы
Размеры итоговой dll сократились примерно в два раза. Неплохо - освободилось место для новых функций :) По итогам работы сделал для себя вывод: надо переходить на 2010-ую студию. Visual C++ 2010 генерирует ощутимо более компактный код, чем Visual C++ 2008.
Очень хорошая статья. Спасибо.
ОтветитьУдалитьCreating the smallest possible PE executable - здесь описывается, как получить рабочий exe-файл размером всего 133 байта.
ОтветитьУдалитьhttp://www.phreedom.org/research/tinype/
cсылка изменилась
Спасибо, исправил.
Удалить