В начало!  
Сделай закладку этой страницы в Digg Сделай закладку этой страницы в Del.icoi.us Сделай закладку этой страницы в Slashdot Сделай закладку этой страницы в Technorati



Улучшаем производительность приложения путем перераспределения кода
Автор Дэррил Гоув   
21.12.2007 г.

Содержание

Большим приложениям присуща определенная проблема: они содержат огромное количество инструкций и емкости процессора становится недостаточно для того, чтобы одновременно уместить все инструкции в кэше. Как следствие, крупные приложения проводят много времени, ожидая, пока процессор загрузит новые инструкции из памяти. Эта статья представляет несколько методик, которые помогают процессору держать больше полезных инструкци в кэше, уменьшая, таким образом, потери времени на ожидание подгрузки данных из памяти.

Но прежде чем приступить к этому, важно понять, что проблема проявляется не только на уровне инструкций. Часто процедуры или используются интенсивно целиком или же наоборот, вызываются крайне редко. Подобным образом, библиотеки могут содержать часто используемые процедуры или требоваться исключительно из-за одного вызова, который почти никогда не происходит.

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

Перегруппировка процедур с использованием map-файлов

Одним из подходов к улучшению ситуации является использование map-файлов. Map-файлы являются средством объяснить компоновщику как разместить процедуры в памяти. Чтобы воспользоваться этим средством, необходимо упорядочить процедуры от наиболее часто используемых к наименее часто используемым. Рисунок 2 показывает программу с процедурами, ранжированными от "горячих" к "холодным", с использованием map-файла.

Map-файлы можно создать вручную, но более простым способом будет воспользоваться инструментом измерения производительности - Performance Analyzer:

  • Постройте программу, используя опцию компилятора -xF

  • Запуститу программу с репрезентативной нагрузкой под collect(1)

  • Сгенерируйте map-файл с помощью er_print -mapfile <app> <mapfilename> (см. er_print(1))

  • Пересоберите приложение с опциями компилятора -xF -M <mapfile> (подробнее об опциях компиляторов см. на соответствующих страницах man - cc(1), CC(1), F95(1) )

Будучи однажды создан, map-файл может переиспользоваться при последующих сборках, пока не изменится профиль использования приложения, не будут переименованы или добавлены новые процедуры.

$ cc -O -xF -o app *.c
$ collect app < test_data
Creating experiment test.1.er ...
$ er_print -mapfile app app.map test.1.er
$ cc -O -xF -M app.map -o app *.c

Табл. 1 - Создание map-файла с использованием инструмента Performance Analyzer

Оптимизация размещения инструкций с учетом данных профилировщика

Map-файлы хорошо работают на уровне процедур, позволяя отделить часто исполняемые процедуры от редко исполняемых. Однако, большая часть времени тратится на уровне инструкций, когда процессор вынужден перепрыгивать через блоки неисполняемого кода. Профилировка - это компиляторная методика улучшения этой ситуации.

Идея использования данных профилировщика заключается в обеспечении компилятора информацией о типичном поведении кода. Основываясь на этих данных, компилятор получает возможность провести следующие типы оптимизаций:

  • Распределить код так, что часто исполняемые участки процедуры оказываются рядом.
  • Встроить (inline) часто вызываемые процедуры, одновременно снимая накладные расходы на вызов процедуры и позволяя далее оптимизировать встроенный код.

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

Рис. 3 показывает как использование данных профилировщика может перераспределить код внутри процедуры.

Воспользоваться этим методом довольно просто:

  • Постройте приложение с опциями -xprofile=collect -xipo
  • Запустите приложение один или несколько раз с репрезентативной нагрузкой
  • Перестройте приложение с опциями -xprofile=use -xipo

Обратите внимание на добавление опции -xipo, позволяющей компилятору проводить кросс-процедурную оптимизацию

$ cc -O -xprofile=collect:app.profile -xipo -o app *.c
$ app < test_data
$ cc -O -xprofile=use:app.profile -xipo -o app *.c

Табл. 2 - Использование данных профилировщика для оптимизации программы

Оптимизация при компоновке

Map-файлы работают на уровне процедур, а компиляция с учетом данных профилировщика работает внутри самих процедур; простым и естественным развитием кажется применение обоих типов оптимизаций одновременно. Это возможно благодаря оптимизации при компоновке (link-time optimiasation), также называемой пост-оптимизацией.

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

  • Так как "горячий" код был идентифицирован, становится возможным его сгруппировать, а затем разместить и весь "холодный" код вместе. Идея в том, чтобы удалить весь "холодный" код из "горячей" области, размещая код из разных процедур в одной области памяти.
  • Возможно также провести дополнительную оптимизацию, учитывая, что адреса переменных и процедур могут быть точно рассчитаны. Следовательно, оптимизатор времени компоновки сможет упростить выражения, вычисляющие адреса переменных или процедур -- это еще больше уменьшит количество инструкций.

Рисунок 4 показывает как будет выглядеть приложение после оптимизации времени компоновки. "Горячий" код будет сгруппирован в одной части библиотеки, а "холодный" в другой, отдельно стоящей, части.

Оптимизация при компоновке требует данных профилировщика, а следовательно, обязательные шаги включают:

  • Построение приложения с опциями компилятора -xprofile=collect -xipo
  • Запуск приложения с репрезентативной нагрузкой один или несколько раз
  • Пересборку приложения с -xprofile=use -xipo -xlinkopt
$ cc -O -xprofile=collect:app.profile -xipo -o app *.c
$ ./app < test_data
$ cc -O -xprofile=use:app.profile -xipo -o app *.c -xlinkopt

Табл. 3 - Сочетания оптимизации времени компоновки и данных профилировщика

Заключение

Использование приведенных методик на больших приложениях может значительно увеличить производительность. Нужно заметить, что ценой применения являются увеличение времени и усложнение процесса сборки; поэтому предварительно следует оценить, стоит ли прирост производительности дополнительных расходов на сборку приложения.

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


Об авторе
Дэррил Гоув - ведущий специалист в группе оптимизации производительности компилятора компании Sun Microsystems Inc. Он занимается анализом и оптимизацией производительности приложений на настоящих и будущих  UltraSPARC-системах. Дэррил имеет степени магистра и доктора наук в области исследования операций Университета Саутгэмптона в Великобритании. Перед тем, как начать работу в Sun, Дэррил занимался различными работами в области проектирования и разработки ПО в Великобритании.

См. также блог Дэррила: http://blogs.sun.com/d/

Воспользоваться описанными в этой статье методиками Вы сможете, скачав компиляторы и инструменты, входящие в состав Sun Studio, по этой ссылке: http://developers.sun.com/sunstudio/downloads/index.jsp.

Оригинал статьи: http://developers.sun.com/solaris/articles/codelayout.html

Перевод: Максим Карташев, 2007г.

 

Добавить комментарий


Защитный код
Обновить