Задачи и возможности оптимизации программного кода для встроенных систем и RISC-процессоров

Трансляторы языков программирования прошли долгий путь эволюционного развития, реализуя самые различные способы повышения эффективности генерируемого кода от использования различных методик оптимизации до поддержки различных модификаций и диалектов языков высокого уровня. Хотя появление стандартов языков программирования, таких как ANSI C, привело к тому, что компиляторы иногда становятся "предметами широкого потребления" со всеми присущими данному эпитету качественными характеристиками, можно выделить два ключевых фактора, определяющих существенные различия в подходах к генерации и оптимизации кода в различных компиляторах. Один из них определяется различиями в архитектурах процессоров: наличие сложных многоступенчатых схем выполнения инструкций в конвейерах, кэши данных и команд, особенности распараллеливания потоков команд приводят к тому, что производительность современных процессоров гораздо сильнее, чем у их предшественников, зависит от структуры объектного кода, сгенерированного компилятором. Второй отражает современные требования рынка программного обеспечения. С появлением у разработчиков ПО новых запросов либо по повышению производительности кода, либо по сокращению его объема, создатели компиляторов вынуждены разрабатывать и реализовывать новые отвечающие этим требованиям технологии.

Общими задачами, решаемыми компиляторами языков высокого уровня (ЯВУ) при разработке встроенных приложений являются:

- повышение производительности исполняемого кода. Использование "тонких" настроек и методик оптимизации позволяет получить код, который выполняется на 20‑30% процентов эффективнее программы, сгенерированной компилятором в режиме "по умолчанию". С учетом того, что во встроенных приложениях (в отличие от desktop-приложений) большую часть времени (более 90%) процессор "выполняет" задачу пользователя, а не обслуживает операционную систему, такой выигрыш является весьма значительным;

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

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

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

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

Следует также иметь в виду, что по сравнению с CISC-процессорами RISC-архитектура позволяет получить значительно больше преимуществ от использования определенных методик оптимизации кода компилятором.

Цели оптимизации кода и способы ее достижения для CISC- и RISC-процессоров существенно различаются вследствие различий в архитектурах процессоров. Наиболее очевидным различием (с точки зрения генерации кода) является различие в системе команд, которая у RISC-процессора обычно значительно беднее (что среди прочего приводит к "разрастанию" кода и повышает требования к объему памяти для хранения программы). Главным преимуществом сокращенной системы команд является возможность их более быстрого декодирования и выполнения. Зачастую все инструкции RISC-процессоров выполняются за один такт и длина всех инструкций одинакова.

В процессорах CISC-архитектуры различия в длительности выполнения инструкций во многом определяются местом размещения операндов. В отличие от RISC-процессоров, выполняющих вычисления обычно только над данными в регистровом файле, CISC-архитектура поддерживает возможность выполнения операций над операндом в памяти (естественно, при этом процессор загружает операнд, выполняет его модификацию и записывает обратно, но с точки зрения программиста для кодирования всех этих операций используется одна инструкция). В зависимости от размещения операндов и результатов в CISC-процессоре варьируется и длительность выполнения инструкции. Например, простейшая команда модификации операнда константой в процессоре Intel семейства 80x86 может занимать от 1 (операнд в регистре) до 3-х тактов (операнд в памяти).

Если сравнить длительности (в тактах) выполнения той же операции модификации в CISC-процессорах фирмы Intel и RISC-процессорах фирмы Analog Devices, то получается следующая картина:

Для SHARC ADSP (RISC) Для Intel 80x86 (CISC)
R1 = dm(X); R2 = 10; R1 = R1 + R2; dm(X) = R1; add X, 10

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

Для этого RISC-процессоры обычно содержат большее количество регистров общего назначения, что дает возможность хранить часть используемые переменные в регистровой памяти, а не обращаться за ними постоянно к внешней памяти или памяти на кристалле. Широко распространены также теневые (альтернативные) регистры, позволяющие быстро переключать контекст задачи без необходимости сохранения/восстановления из памяти большого числа регистров.

Еще одним элементом архитектуры (правда, уже общим для RISC- и CISC-процессоров) является многоступенчатый конвейер команд, позволяющий выполнять одновременно выборку, декодирование и выполнение инструкций.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: