Оптимизация для процессоров семейства Pentium: 17. Выполнение кода не по порядку (PPro, PII и PIII) — Архив WASM.RU
Буфер перегруппировки вмещает 40 мопов. Каждый моп ждет в ROB, пока все операнды не будут готовы и не появится свободный модуль для их выполнения. Это делает возможным выполнение кода не по порядку. Если одна часть кода задерживается из-за загрузки в кэш, это не повлияет на выполнение других частей кода, если они не зависят от первой части.
Запись в память не может быть выполнена по порядку, если она зависит от других записей. Есть четыре буфера записи, поэтому вы можете ожидать большие потери производительности во время такой записи в память или записи в некэшированную память, поэтому рекомендуется, чтобы вы делали четыре записи за раз и были уверены в том, что у процессора есть чем еще заняться, прежде чем вы нагрузите его еще четырьмя записями. Чтение из памяти и другие инструкции могут выполняться не по порядку, кроме IN, OUT и синхронизирующих операций.
Если ваш код пишет в память по какому-то адресу, а вскоре считывает оттуда же, тогда чтение по ошибке может быть произведено до записи, потому ROB не имеет понятия об адресах во время перегруппировки. Эта ошибка отлавливается, когда подсчитывается адрес памяти, поэтому операция чтения в таком случае выполняется снова. Потери при этом составляют 3 такта. Единственный путь избежать этого - это убедиться, что у модуля выполнения будет занят делом между последовательными записью в память и чтением из нее по тому же адресу.
Есть несколько модулей выполнения, сгруппированных вокруг пяти портов. Порт 0 и 1 - для арифметических операций. Простые пересылки, арифметические и логические операции попадают в порт 0 или 1, в зависимости от того, какой из них свободен. Порт 0 также обрабатывает умножение, деление, целочисленные побитовые сдвиги и операции с плавающей запятой. Порт 1 в дополнение к основным функциям занимается переходами и некоторыми операциями MMX и XMM. Порт 2 обрабатывает все чтения из памяти и несколько строковых операций. В главе 29 вы найдете полный список мопов, генерируемых инструкциями кода с указанием того, в какой порт они попадают. Заметьте, что все операции записи в память требуют два мопа, один для порта 3, а другой для порта 4, в то время как операции чтения из памяти требуют только один моп (порт 2).
В большинстве случаев каждый порт может получать один моп за такт. Это означает, что вы можете выполнять до 5 операций мопов за такт, если они попадают в пять разных портов, но так как есть ограничение на 3 мопа за такт, установленное ранее в конвеере, вам никогда не удастся выполнить больше 3 мопов за такт в среднем.
Вы должны убедиться, что никакой порт не получает больше одной трети мопов, если вы хотите достичь производительности в 3 мопа за такт. Используйте таблицу мопов в главе 29 и подсчитайте, сколько мопов попадет в каждый порт. Если порты 0 и 1 перегружены, в то время как порт 2 свободен, вы можете улучшить свой код, заменив некоторые пересылки вида "регистр, регистр" или "регистр, числовое значение" на пересылку вида "регистр, память", чтобы перенести часть нагрузки с портов 0 и 1 на порт 2.
Большинство мопов занимают один такт для выполнения, но умножения, деления и многие операции с плавающей запятой занимают больше:
Сложения и вычитания плавающей запятой занимают 3 такта, но модуль выполнения полностью конвееризован, поэтому он может принимать новые FADD и FSUB каждый такт, пока предыдущие инструкции выполняются (конечно, если они независимы друг от друга).
Целочисленное умножение занимает 4 такта, умножения плавающей запятой - 5, а умножения MMX - 3 такта. Умножения целых чисел и MMX конвееризованы, поэтому они могут получать новые инструкции каждый такт. Умножения плавающей запятой частично конвееризованы: модуль выполнения может получать новую инструкцию FMUL через два такта после получения предыдущей, поэтому максимальная производительность будет равная одному FMUL за каждые два такта. "Дыры" между FMUL'ами нельзя заполнить целочисленными умножениями, так как они используют ту же схему. Сложения и умножения XMM занимают 3 и 4 такта соотвественно и полностью конвееризованы. Но так как каждый регистр XMM представлен в виде двух 64-х битных регистров, упакованная инструкция XMM будет состоять из двух мопов, а производительность будет равна одной арифметической инструкции XMM каждые два такта. Инструкции сложения и умножения XMM могут выполняться параллельно, потому что оне не используют одни и те же порты.
Деление целых чисел и чисел с плавающей запятой занимает до 39 тактов и не конвееризовано. Это означает, что модуль выполнения не может начать новое деление, пока предыдущие не будет выполнено. Вышесказанное относится к квадратному корню и трансцендентальным функциям.
Также инструкции переходов, вызовов и возвратов не полностью конвееризованы. Вы не можете выполнить новый переход в первом же такте после предыдущего перехода. Поэтому максимальная производительность для переходов, вызовов и возвратов равна одному за каждые два такта.
Конечно вы должны избегать инструкций, которые генерируют много мопов. Например инструкцию LOOP XX нужно заменить на DEC ECX / JNZ XX.
Если у вас есть две последовательные POP-инструкции, вы можете заменить их на другие, чтобы снизить количество мопов.
Код (Text):
POP ECX / POP EBX / POP EAX ; можно заменить на: MOV ECX,[ESP] / MOV EBX,[ESP+4] / MOV EAX,[ESP] / ADD ESP,12Первый код генерирует 6 мопов, последующий - 4 и декодируется быстрее. Делать то же самое с инструкциями PUSH менее выгодно, потому разбитый код будет генерировать задержки чтения регистров, если только у вас нет других инструкций, которые можно вставить между ними или какие-то регистры не были переименованы раньше. Делать подобное с инструкциями CALL и RET означает мешать правильному предсказанию перехода. Также обратите внимание, что 'ADD ESP' может вызвать задержку AGI в более ранних моделях процессоров. © Агнер Фог, пер. Aquila
Оптимизация для процессоров семейства Pentium: 17. Выполнение кода не по порядку (PPro, PII и PIII)
Дата публикации 22 авг 2002