Профилирование на уровне инструкций: значение указателя инструкций?

При профилировании кода на уровне инструкций на ассемблере, что в действительности означает положение указателя инструкций, учитывая, что современные процессоры не выполняют команды последовательно или по порядку? Например, предположим следующий код сборки x64:

mov RAX, [RBX];         // Assume a cache miss here.
mov RSI, [RBX + RCX];   // Another cache miss.             
xor R8, R8;        
add RDX, RAX;           // Dependent on the load into RAX.
add RDI, RSI;           // Dependent on the load into RSI.

На какую инструкцию будет тратить указатель инструкции большую часть своего времени? Я могу придумать хорошие аргументы для всех них:

  • mov RAX, [RBX] занимает, вероятно, 100 с циклов, потому что это промах кеша.
  • mov RSI, [RBX + RCX] также занимает 100 с циклов, но, вероятно, выполняется параллельно с предыдущей инструкцией. Что вообще означает, что указатель инструкции находится на одном или другом из них?
  • xor R8, R8 возможно, выполняется не по порядку и завершается до завершения загрузки памяти, но указатель инструкций может оставаться здесь до тех пор, пока все предыдущие инструкции также не будут завершены.
  • add RDX, RAX создает остановку конвейера, потому что это инструкция, где значение RAX фактически используется после медленной загрузки кэша в него.
  • add RDI, RSI также глохнет, потому что это зависит от нагрузки в RSI,

1 ответ

Решение

Процессоры поддерживают фикцию, что есть только архитектурные регистры (RAX, RBX и т. Д.), И есть специальный указатель инструкций (IP). Программисты и составители предназначаются для этой беллетристики.

Тем не менее, как вы заметили, современные процессоры не работают последовательно или по порядку. Пока вы не программист / пользователь не запросите IP, он похож на квантовую физику, IP - это волна выполняемых инструкций; все для того, чтобы процессор мог запустить программу как можно быстрее. Когда вы запрашиваете текущий IP (например, через точку останова отладчика или прерывание профилировщика), тогда процессор должен воссоздать ожидаемый вымысел, чтобы он свернул эту волновую форму (все инструкции "в полете"), собрал значения регистра обратно в архитектурные имена и создает контекст для выполнения процедуры отладчика и т. д.

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

Например, возможно, прерывание указывает mov RSI, [RBX + RCX]; как IP, но xor уже выполнен и завершен; однако, когда процессор возобновит выполнение после прерывания, он повторно выполнит xor.

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

Ваш самый большой враг - это склонность к самовосхвалению: "Я бы сознательно не писал никаких ошибок. Зачем мне?" Конечно, вы знаете, почему вы тестируете программное обеспечение. Но то же самое касается ошибок скорости, и если вы не знаете, как найти те, которые, по вашему мнению , их нет, то это способ сказать: "Мой код не имеет возможных ускорений, за исключением, может быть, профилировщик может показать мне, как сбрить несколько циклов ".

По моему полувековому опыту, не существует кода, который в том виде, в котором он был написан впервые, не содержал бы ошибок скорости. Более того, есть огромный эффект умножения, когда каждая ошибка скорости, которую вы удаляете, делает оставшиеся более очевидными. В качестве надуманного примера предположим, что ошибка A составляет 90% времени, а ошибка B составляет 9%. Если вы исправите только B, большое дело - код на 11% быстрее. Если вы исправите только A, это хорошо - это в 10 раз быстрее. Но если вы исправите оба, это действительно хорошо - это в 100 раз быстрее. Исправление A сделало B большим.

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

Другие вопросы по тегам