![]() |
[Кодинг] Основы низкоуровневой оптимизации
Примеры, описанные в статье, носят исключительно академический характер.
Всем привет. Хотелось бы рассказать об основах этого нелегкого, но интереснейшего занятия. В качестве рабочего инструмента я буду использовать язык программирования C++, а конкретнее — компилятор g++. Так как само название «низкоуровневой оптимизации» говорит нам о том, что работать мы будем на уровне языка ассемблера, то стоит заметить, что я использую ОС GNU/Linux, а значит мы будем иметь дело не стандартным синтаксисом INTEL, а с синтаксисом AT&T, немного отличающимся от привычного нам кода, например, в MASM или TASM. Перед константными выражениями в соответствии с правилами синтаксиса AT&T мы будем ставить знак $, перед именами регистров – %, а в командах пересылки на первое место ставится источник, на второе — назначение (в синтаксисе INTEL всё как раз наоборот). Синтаксис AT&T может показаться совершенно непонятным на первый взгляд, а, может быть, даже неуклюжим и глупым. Но лично я считаю его более удобным и совершенным: логичнее указывать источник первее, чем назначение, а обозначение регистров со знаком % и констант со знаком $ в конце-концов делает код более читабельным и простым для восприятия. Кроме того, стоит заметить, что так же я использую архитектуру x86_64, а значит, если Вы работаете в 32-разрядной операционной системе, то в некоторых моментах код для Вашей платформы может немного отличаться от моего. На этом закончим вступительную часть и приступим непосредственно к теме статьи. Общая информация Код на C++ будет переведён компилятором в машинные команды, а значит, скорее всего, может быть переведён в код на языке Assembler. Так оно и есть. Для этого укажите компилятору опцию -S: Код:
g++ test.cpp -S Чем нам может быть полезен код на ассемблере? А тем, что компилятор реализует инструкции «общим случаем», т.е. таким образом, чтобы они работали всегда. Отсюда и идёт некоторая потеря производительности: то тут лишняя инструкция влезла, тот там ещё лишние пять. Давайте сразу рассмотрим пример: Код:
int main() Код:
g++ test.cpp -S Код:
.file «test.cpp» Код:
movl $0, -4(%rbp) Код:
for (register int i=0; i<10; i++); Вот, собственно, мы и сделали первый шаг, используя метод низкоуровневой оптимизации и имеем следующий код: Код:
.file «test.cpp» Код:
addl $1, %ebx Код:
inc %ebx Достаточно вспомнить про инструкцию loop, используемую специально для циклов со счётчиком типа for. Немного напомню о принципе работы loop для тех, кто о нём забыл и тех, кто о нём даже не подозревал. Все циклы со счётчиком имеют следующую конструкцию: Код:
movl $10, %ecx Код:
mov $10, %ecx Учитывая всё вышесказанное, оптимизируем код нашей программы так: Код:
.file «test.cpp» Asm-функции на основе шаблонов C++ Конечно, оптимизация отдельно взятых кусков кода — это большой плюс к производительности. Но Вы можете самостоятельно реализовать на ассемблере целые функции (процедуры). Рассмотрим пример: Код:
void sum(int a, int b, int *c) { } Код:
.file «test.cpp» Реализация алгоритма функции на языке ассемблера выглядит так: Код:
/* Значение первого параметра перемещаем в %eax */ Теперь немного о параметрах. Компиляторы, например от Microsoft или Borland, передают параметры в функцию через стек таким образом, что первым туда попадает крайний правый параметр, а последним — крайний левый. Возвращает же значение функция через регистр ax (для 16-битных компиляторов, для других — через соответствующие регистры). В случае компилятора g++ вопрос передачи параметров решается немного по-другому. Попробуйте сами сгенерировать код, содержащий передачу параметров функции в количестве от одного до десяти, и посмотреть как это делается. Уверяю Вас, это действительно интересно! Вообще говоря, изучайте свою систему, свой компилятор. Потому что даже разные версии одного и того же компилятора могут генерировать разный код. Заключение На этом я, пожалуй, закончу. Надеюсь, мне удалось показать вам основы низкоуровневой оптимизации в той мере, которая необходима для понимания данного метода. Использование возможностей ассемблера при программировании на языках высокого уровня даёт вашей программе не только прирост в скорости и эффективности. Вы так же можете выполнять аппаратно-зависимые функции, недоступные в C/C++ или других языках. Кроме того, может возникнуть ситуация, когда Вам будет необходимо обойти запреты ООП или операционной системы. Команды процессора «прорвут» инкапсуляцию, даже не подозревая о её существовании. Экспериментируйте с ассемблерными вставками и вы узнаете о своём компиляторе массу новой и полезной информации. |
Хм, а есть готовые решения для оптимизации по мелочам?
Я хочу сказать, что вряд ли программисты в серьезных проектах будут рады видеть ассемблерные вставки среди фабрик фабрик фабрик. А вот если бы в коде был декоратор @{Оптимизируй это}, то было бы гораздо удобнее же. PS: Декоратор, а не глобальный оптимизатор, так как проще компилятору сказать, что эта переменная нужна только для цикла, чем обучать его, что надо оптимизировать, а что - нет. |
Можно оптимизировать отдельные функции:
Цитата:
|
Часовой пояс GMT +3, время: 14:36. |
Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2022, Jelsoft Enterprises Ltd. Перевод: zCarot