PDA

Просмотр полной версии : Основы низкоуровневой оптимизации


herfleisch
27.07.2010, 12:59
Мне очень запомнилось высказывание Михаила Флёнова в одной из его книг: «Если Вы написали программу, то Вы программист. Но если Ваша программа лучше других, то Вы хакер. Оптимизация — один из способов сделать программу максимально быстрой, эффективной и выделяющейся на фоне остальных».

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

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

Я расскажу Вам об основах этого нелёгкого, но интереснейшего занятия. В качестве рабочего инструмента я буду использовать язык программирования C++, а конкретнее — компилятор g++. Так как само название «низкоуровневой оптимизации» говорит нам о том, что работать мы будем на уровне языка ассемблера, то стоит заметить, что я использую ОС GNU/Linux, а значит мы будем иметь дело не стандартным синтаксисом INTEL, а с синтаксисом AT&T, немного отличающимся от привычного нам кода, например, в MASM или TASM. Перед константными выражениями в соответствии с правилами синтаксиса AT&T мы будем ставить знак $, перед именами регистров – %, а в командах пересылки на первое место ставится источник, на второе — назначение (в синтаксисе INTEL всё как раз наоборот).

Синтаксис AT&T может показаться совершенно непонятным на первый взгляд, а, может быть, даже неуклюжим и глупым. Но лично я считаю его более удобным и совершенным: логичнее указывать источник первее, чем назначение, а обозначение регистров со знаком % и констант со знаком $ в конце-концов делает код более читабельным и простым для восприятия.

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

На этом закончим вступительную часть и приступим непосредственно к теме статьи.

Общая информация

Код на C++ будет переведён компилятором в машинные команды, а значит, скорее всего, может быть переведён в код на языке Assembler. Так оно и есть. Для этого укажите компилятору опцию -S:


g++ test.cpp -S

и он создаст в текущем каталоге файл test.s, содержащий конвертированный код.

Чем нам может быть полезен код на ассемблере? А тем, что компилятор реализует инструкции «общим случаем», т.е. таким образом, чтобы они работали всегда. Отсюда и идёт некоторая потеря производительности: то тут лишняя инструкция влезла, тот там ещё лишние пять. Давайте сразу рассмотрим пример:

[CODE]
int main()
{
for (int i=0; i