2013-08-31 3 views
2

Начиная с 32-разрядного режима ЦП, для архитектуры x86 доступны расширенные адресные операнды. Можно указать базовый адрес, смещение, индексный регистр и коэффициент масштабирования.Как операнд адреса влияет на производительность и размер машинного кода?

Например, мы хотели бы шагать через список 32-битные целые числа (каждый первые два из массива 32-байтовые длинных структур данных, %rdi как индекс данных, %rbx в качестве указателя базы).

addl $8, %rdi    # skip eight values: advance index by 8 
movl (%rbx, %rdi, 4), %eax # load data: pointer + scaled index 
movl 4(%rbx, %rdi, 4), %edx # load data: pointer + scaled index + displacement 

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

addl $32, %rbx  # skip eight values: move pointer forward by 32 bytes 
movl (%rbx), %eax # load data: pointer 
addl $4, %rbx  # point next value: move pointer forward by 4 bytes 
movl (%rbx), %edx # load data: pointer 

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

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

Или, спрашивая его с точки зрения программиста C, что быстрее: индексирование массива или арифметика указателя?


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

+1

Общий ответ # 0: Оптимизация - это вуду, и такие вещи, как добавление инструкций или использование более длинных инструкций, могут ускорить работу в некоторых случаях. Такое поведение может варьироваться от CPU к CPU; что-то истинное на одной модели может быть неверным на более новой модели. В вашем случае все может идти в любом случае, и нет никакого хорошего способа предсказать без простого измерения. – Nayuki

+1

Общий ответ №1: http://www.agner.org/optimize/; http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html – Nayuki

+0

@NayukiMinase, некоторые v полезные ссылки. Очень стоит просмотреть. Благодарю. – TerryE

ответ

1

Арифметика адресов очень быстрая и должна использоваться всегда, если это возможно.

Но вот что-то, о чем пропустил вопрос.

Сначала вы не можете умножить на 32, используя адресную арифметику - 8 - максимально возможная константа.

Первая версия кода не завершена, так как ей потребуется вторая инструкция, чтобы увеличить значение rbx. Итак, мы имеем следующие два варианта:

inc rbx   
mov eax, [8*rbx+rdi] 

против

add rbx, 8 
mov eax, [rbx] 

Таким образом, скорость обоих вариантов будет то же самое. Размер тот же - 6 байтов.

Итак, какой код лучше, зависит только от контекста программы - если у нас есть регистр, который уже содержит адрес нужен массив ячеек - использование MOV EAX, [RBX]

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

+0

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

+0

@KrzysztofAbramowicz Точно. Не забудьте проголосовать и принять ответ, который вам нравится. ;) – johnfound

+0

Что касается умножения, нет ошибки, но пример вводит (ненужное) предположение, что '% rdi' выполняет 8 единиц на итерацию, поэтому коэффициент масштабирования равен 4, но данные не получают доступ линейно. Было бы очевидно, если бы я не забыл предоставить инструкцию по продвижению цикла для первого варианта. Я буду применять ваши замечания к моему вопросу. –

2

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

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

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

Вы также можете скачать исходный код gcc и посмотреть, как действительно крутые программисты оценивают последовательность, чтобы создать максимально возможный код. Однажды вы можете стать одним из них :-)

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

+0

Вы правы - я должен был предоставить более широкий пример для моего вопроса. Мое намерение состояло в том, чтобы сосредоточиться на арифметической производительности адреса на уровне инструкции (выполняемой выделенным подразделением на другом этапе конвейера, правильно?) И его сопоставлением с независимыми вычислениями на основе ALU, выполненными по той же причине. Тем не менее, вопрос возник из более широкой проблемы, которую я собираюсь раскрыть посредством редактирования. –

+0

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

+1

. В одном месте, где тестирование, например, одно (я думаю), вы предлагаете, полезно определить эффективность ALU или FPU при сравнении разных процессоров/архитектур. Возможно, вы захотите проверить этот документ (http://gmplib.org/~tege/x86-timing.pdf), который показывает скорость, с которой некоторые архитектуры x86 могут обрабатывать отдельные инструкции и степень их параллелизма. –