Вадим Егоров
zg@matrica.apollo.lvВ данной статье рассматривается вопрос об уменьшении размера плагинов, скомпилированных gcc.
Условия, при которых возможен существенный выигрыш в размере:
1. Вы явно или неявно используете в своем плагине оператор new.
2. Вы не используете обработку исключений и механизм rtti.
Для компиляции использовались gcc 2.95.3-4 и ld 2.11.90 (20010704).
test.cpp:
#include "plugin.hpp"
void WINAPI _export SetStartupInfo(const struct PluginStartupInfo *Info)
{
int *a;
a=new int;
delete a;
}
test.def:
EXPORTS SetStartupInfo=SetStartupInfo@4
makefile:
CFLAGS=-c -mcpu=pentiumpro -O3 -Wall LFLAGS=-s -mdll RM=rm CC=gcc DLLTOOL=dlltool DEF=test.def OBJS=test.o TARGET=test.dll all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(LFLAGS) -o nul -Xlinker --base-file -Xlinker $@.base $^ $(LIBS) $(DLLTOOL) --dllname $@ --base-file $@.base --output-exp $@.exp --def $(DEF) $(CC) $(LFLAGS) -o $@ $^ $@.exp $(LIBS) $(RM) $@.base $(RM) $@.exp test.o: test.cpp $(CC) $(CFLAGS) $< -o $@
Примечание: -mcpu=pentiumpro означает всего лишь оптимизацию для P6+ процессоров, без использования команд отсутствующих в i386.
Запускаем make. Получаем test.dll размером 23552 байт. Незаслуженно много для такого простого плагина. Начинаем оптимизацию.
Шаг 1. На этом шаге из плагина исключается код для поддержки исключений и rtti. Добавляем в makefile:
CFLAGS=-c -mcpu=pentiumpro -O3 -Wall -fno-rtti -fno-exceptions
Но этого недостаточно. Дело в том, что в реализации глобальных операторов new и delete используются и исключения и rtti. Поэтому добавим в плагин свои версии этих операторов:
void * operator new(size_t size)
{
return HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,size);
}
void operator delete(void *block)
{
if(block) HeapFree(GetProcessHeap(),0,block);
}
Запускаем make. Получаем test.dll размером 3584 байт.
Если вы используете операторы new[] и delete[], то их тоже надо переписать:
void * operator new[](size_t size)
{
return HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,size);
}
void operator delete[](void *block)
{
if(block) HeapFree(GetProcessHeap(),0,block);
}
Шаг 2. Исключение startup code. Его размер в версии gcc, указанной выше, составляет 268 байт. Не так уж много, но все же. Добавляем в makefile:
LFLAGS=-s -mdll -nostartfiles
...и в test.cpp:
#ifdef __cplusplus
extern "C"{
#endif
BOOL WINAPI DllMainCRTStartup(HANDLE hDll,DWORD dwReason,LPVOID lpReserved);
#ifdef __cplusplus
};
#endif
BOOL WINAPI DllMainCRTStartup(HANDLE hDll,DWORD dwReason,LPVOID lpReserved)
{
return TRUE;
}
Запускаем make. Получаем test.dll размером 3072 байт.
Для этого плагина на этом можно и остановится. Но для реальных плагинов можно еще сэкономить килобайт, другой.
Шаг 3. Дополнительные флаги оптимизации.
Далее идет описание флагов, влияющих на оптимизацию, но не включенных в -O3. Ими надо пользоваться с осторожностью. Иногда может быть получен неожидаемый результат.
-fomit-frame-pointer - без этого флага для каждой функции генерируется пролог и эпилог примерно следующего вида:
push ebp mov ebp,esp ... mov esp,ebp pop ebp
Использование данного флага позволяет исключить генерацию пролога и эпилога без необходимости. Что при большом числе функций в проекте, позволяет получить видимый выигрыш в размере.
-fstrict-aliasing - включает оптимизацию, основываясь на типе выражения. Считается что указатели разных типов указываю на разные участки памяти. Исключение составляют поля в объединениях.
-funroll-loops - оптимизация по раскрытию циклов с заранее известным количеством итераций. Может приводить к увеличению размера кода.
-funroll-all-loops - оптимизация по раскрытию всех циклов. Может приводить к увеличению размера и увеличению времени выполнения кода.
-fno-builtin - для таких функций, как abort, abs, alloca, cos, exit, fabs, ffs, labs, memcmp, memcpy, sin, sqrt, strcmp, strcpy и strlen gcc по умолчанию вместо вызова библиотечных функций генерирует inline код. Данный флаг предотвращает это, уменьшая размер кода, но при этом увеличивая время выполнения.
16.07.2001