2010-09-22 2 views
13

Я сейчас в процессе написания compiler, и у меня, похоже, возникли проблемы с получением кода вывода, который выполняется в приличном таймфрейме.Каковы некоторые советы по оптимизации кода сборки, сгенерированного компилятором?

Краткий обзор компилятора:

7Basic является компилятор, который ставит своей целью собрать 7Basic кода непосредственно в машинный код для целевой архитектуры/платформы. В настоящее время 7Basic генерирует сборку x86 с исходным файлом.

Проблема заключается в том, что сборка код, генерируемый компилятором медленно и неэффективно.

Например, код this (который составляет до this код сборки) занимает почти 80,47 раз дольше, чем the equivalent C code.

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

push eax 
push 5000000 
pop ebx 
pop eax 

Вместо того, чтобы более логично:

mov ebx,5000000 

... который выполняет то же самое.

Мой вопрос: какие методы избегают такого рода проблем? Парсер в основном использует рекурсию для анализа выражений, поэтому генерируемый код отражает это.

+13

Добавить -O3 в вашу командную строку компилятора ;-) – hirschhornsalz

+0

+1 @drhirsch LOL – karlphillip

+3

«... ** кросс-платформенный ** BASIC-компилятор, который генерирует собственные ** исполняемые файлы ** Win32 **». Чего ждать? –

ответ

14

Один метод называется peephole optimisation. Это требует итеративного подхода к очистке кода сборки. По сути, вы просматриваете код сборки, просматривая только две или три команды за раз, и видите, можете ли вы уменьшить их на что-то более простое. Например,

push eax  ; 1 
push 5000000 ; 2 
pop ebx   ; 3 
pop eax   ; 4 

Первым шагом будет смотреть на линии 2 и 3, и заменить это:

push eax  ; 1 
mov ebx,5000000 ; 2a 
pop eax   ; 4 

Во-вторых, вы могли бы рассмотреть 1 и 4, а если eax не затрагивается в средний инструкция, удалить их обоих, оставив то, что вы хотите:

mov ebx,5000000 ; 2a 
+0

+1: избили меня до этого ... –

+0

Хорошо, это можно сделать, поскольку код генерируется? Это было бы лучше. –

+0

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

5

вы можете рассмотреть вопрос о генерации кода C, а не сборка, а затем пусть C компилятор (например, GCC) обрабатывать код г для вас. Нет смысла пытаться изобрести колесо.

+0

В конечном итоге компилятор будет генерировать машинный код, поэтому это не вариант. –

+2

В конце концов компилятор C также будет генерировать машинный код. –

+0

Что я имел в виду, так это то, что в конечном итоге компилятор будет напрямую генерировать машинный код. –

2

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

Этот шаблон испускаемого кода подсказывает мне, что генератор кода не знает, что x86 имеет инструкции «mov немедленного», которые непосредственно вставляют постоянное значение в поток команд. Кодировка x86 для кодов операций с немедленными значениями может немного усложниться (байты R/M переменной длины), но это уже необходимо, если вы хотите использовать многие инструкции x86.

Этот испущенный код также предполагает, что генератор кода не знает, что EAX не изменяется инструкциями EBX. Это похоже на то, что codegen является шаблоном, а не дискретной логикой.

Этот вид codegen возникает, когда внутреннее промежуточное представление операций компилятора недостаточно детально, чтобы представлять все грани целевой архитектуры. Это особенно верно, если архитектура генератора кода была первоначально разработана для набора команд RISC, но была перепрофилирована для извлечения инструкций x86. Архитектура RISC, как правило, имеет очень мало и очень простая загрузка, хранение и управление инструкциями reg/reg, тогда как набор инструкций x86 органично развивается в течение десятилетий, чтобы включать в себя множество кодов операций, которые непосредственно работают с памятью, встроенными константами в инструкции, и весь беспорядок других вещей. Если промежуточное представление компилятора (граф выражений) подключено к RISC, будет сложно сделать его доступным для широкого разнообразия и тонкостей x86.

+0

На самом деле я написал генератор кода :) –

+0

Прохладный. Тогда есть надежда, что этот кодеген может быть улучшен. ;> Шаг 1: выяснить, как распознавать постоянные нагрузки в промежуточном представлении и испускать их как mov reg, imm. Шаг 2: выясните, почему ваш генератор кода нажимает и выскакивает eax в этом примере, поскольку он вообще не имеет отношения к основной операции. Пахнет ошибкой. – dthorpe

+0

Это не ошибка. Он должен делать это просто из-за того, как оцениваются выражения. Вот почему я задал вопрос. –

3

В настоящее время я беру курс компилятора. Я сделал большой прогресс в выпуске эффективного кода, но вы должны заглянуть в книгу драконов. Это обряд посвящения. Вы должны взглянуть на код из книги Джереми Беннетта, Введение в методы компиляции: первый курс с использованием ANSI C, LEX и YACC. Сама книга очень трудно найти, но вы можете загрузить исходный код для компилятора, свободного от

http://www.jeremybennett.com/publications/download.html

Файл генератор кода (cg.c) имеет некоторые функции для генерации достаточно оптимизированный код. Целевой язык не i386, но вы должны рассмотреть вопрос о том, как он описывает регистры и отслеживает, где хранятся записи таблицы символов. Его выходная сборка может быть дополнительно оптимизирована, но она обеспечивает отличную основу для создания кода, который может конкурировать с выходом gcc -S в некоторых отношениях.

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

Например, если ваш промежуточный код представляет собой список четверки, вы должны просто итератором через него для каждой функции и отслеживать максимальное смещение. Затем выведите строку, чтобы вычесть объем пространства в стеке. Это устраняет необходимость включения и выключения большого количества переменных. Чтобы удалить их, вы можете просто переместить их значение из их смещения в стек в регистр. Это значительно улучшит производительность.

+0

Отличный совет - язык еще не имеет понятия сферы видимости и не имеет функций/подпрограмм. Все еще идет работа. Но когда это произойдет, я обязательно буду иметь локальные переменные в стеке. –

+0

Каково ваше промежуточное представление кода? TAC/четверки? – Kizaru

+0

У него нет :) Компилятор отправляет «псевдо-команды» в выходной модуль, который генерирует точные инструкции по сборке. –

2

Оптимизация Peephole поможет, но одна очевидная проблема заключается в том, что ваш компилятор не выполняет распределение регистров!

http://en.wikipedia.org/wiki/Register_allocation

Если вы хотите, чтобы получить серьезные уровни производительности, что вы делаете, чтобы смотреть на это. Это можно сделать за один проход, если вы делаете это с жадностью «на лету».

 Смежные вопросы

  • Нет связанных вопросов^_^