Инструменты Intel для анализа производительности
На прошлой нашей встрече мы разговаривали о том, как работает процессор и какие факторы влияют на его производительность. Соответственно, чтобы выжать максимальную мощность из вашего процессора, вы должны эти факторы знать и должны понимать, как бороться с теми или иными недостатками программы.
У нас курс несколько сокращенный, по полному плану предполагалось рассказывать целую лекцию про Vtune, но мы решили, что интереснее будет разговаривать про компилятор, а про Vtune мы сделаем просто короткий рассказ.
Итак, на чем базируются инструменты по анализу производительности. Дело в том, что процессор содержит массу специализированных событий, сигналов, которые появляются в тот момент, когда происходит какое-то конкретное событие. То есть случился у вас промах по кэшу— у вас появилось событие, которое можно улавливать и можно на него реагировать. Случилось у вас неугадывание ветвления – тоже, значит, событие. Соответственно, мы берем некий инструмент, который общается с драйвером, улавливающим эти события. Работа происходит следующим образом: мы запускаем программу и ставим прерывание на происхождение того или иного события. Какое прерывание происходит — это достаточно сложный вопрос, но в Vtune выставляет какие-то значения по умолчанию, видимо, довольно разумные.
И каждый раз, когда в процессе выполнения программы происходит такое событие, происходит прерывание, и программа смотрит, при исполнении какой инструкции произошло это событие. Ну и, соответственно, оно ведет статистику, собирает привязку происходящих событий. Как правило, за одно выполнение можно собирать несколько разных событий. Самое широкое используемое событие — это, по сути, время. По прохождения какого-то количество миллисекунд происходит прерывание, и мы смотрим, в каком месте, на какой инструкции мы находимся и количество клок тиков увеличиваем на один.
В результате, после того, как программа выполнена, мы получаем полную картину: на какую инструкцию сколько клок тиков пришлось. Таким методом мы можем определить, в каких функциях наша программа больше всего времени потратила, в какие она вроде как даже и не заходила. Ну, это некое статистическое [исследование], то есть, поскольку мы не каждое событие отслеживаем, есть некая вероятность того, что наши данные немножко неточны. В целом, они должны раскладываться более-менее разумно. И вот, на основании этого, мы получаем некоторые сведения о том, сколько потратилось времени внутри каждой функции. Поскольку у нас обычно программа содержит информацию о функциях, которые в ней есть, то по умолчанию Vtune способен сделать привязку вот этих временных событий к функциям. В результате у нас получается список функций по времени, которое было в них затрачено, список горячих функций.
Есть такая теорема, что в десяти процентах кода, как правило, содержится 90% производительности. Поэтому разумно, если вы занимаетесь разработкой вашего приложения, трать время эффективно, и есть перед вами есть цель: улучшение производительности, вы должны, в первую очередь, найти функции, которые на нее влияют, и после этого именно с этими функциями и работать. Пытаться оптимизировать всю программу, наверное, не имеет смысла.
То же самое касается всех других событий. Обычно ситуация примерно какая: после того, как вы собрали и получили список горячих функций, вам интересно понять, а какая причина того, что именно столько времени тратится в этой функции? Это нужно для того, чтобы продумать те шаги, которые вам необходимо сделать для решения этой проблемы (чтобы уменьшить время, затрачиваемое на этой функции). И вот, если вам сразу никаких идей не приходит, вы можете проверить, например, как данный фрагмент программы работает с кэшем. То есть, вы включаете специальный набор событий, которые имеют промахи по кэшу, или попадание по кэшу. Эти события от процессора к процессору меняются, но вот этот vtune (инструмент) – он, как правило, содержит какие-то шаблоны (более-менее приемлемые). То есть там написан шаблон, который вам позволяет анализировать работу памяти: вы запускаете формально этот шаблон, он собирает какое-то количество событий и показывает. Вы смотрите: ага, здесь видно, что очень много промахов по кэшу и думаете, из-за чего их много. Либо вы видите, что здесь проблема в том, что мы не правильно предсказывали переходы и так далее. Первый шаг – собрать код функции, если у вас от изучения вашего исходного кода нет понимания того, из-за чего так много времени тратиться, соответственно, начинаете вести более углубленный анализ производительности.
Бывают случаи, конечно, когда вы получили всю возможную производительность и ничего сделать нельзя. Ну, тут, сделав какие-то эксперименты, вы поймете, насколько реально сложно увеличить производительность вашего приложения.
Vtune существует на всех компьютерах, на которых существует наш компилятор, формально это MacOS, Windows и Linux. При этом форматы данных, которые собирает инструмент, одинаковые. И существуют даже командные утилиты, поскольку, допустим, на Linux графический интерфейс менее качественный, менее удобный, то вы можете просто с помощью командной утилиты собрать файлбан, переместить его на Windows, и на Windows сидеть и анализировать, что происходит на том процессоре.
Соответственно, я уже сказал, что по умолчанию вы получите горячие функции и ассемблер. Вам будет доступен ассемблер, и если вы знаток ассемблера, вы на него посмотрите и вам станет все понятно. Если вы не знаток ассемблера, то есть такая дебагерная информация, по сути дела, привязка инструкции к линиям кода. Ну вот, допустим, у нашего компилятора есть специальные ключи, которые называются минус дебаг. С этим ключиком просто к экзешнику добавляется эта минимальная привязка инструкции к строкам кода. После этого в программе, которую вы будете изучать под vtune'ом, у вас получится привязка к строкам кода. Понятно, что она несколько условная, потому что поскольку компилятор оптимизирующий, он зачастую делает очень хитрые вещи и может делать со строкой кода какие-то чудесные превращения. (10.50)
Например, vtune может построить динамический граф вызовов, то есть то, что более-менее реально происходит при выполнении вашей программы с каким-то набором данных. Если у вас какая-то маленькая функция стала горячей, вам интересно, из каких мест она чаще всего вызывается. То есть, она вызывается в ста местах, а горящая она, допустим, в трех местах. При этом формально в каждом из этих трех мест ей могут передаваться различные аргументы с различными свойствами. Иногда, чтобы сделать оптимизацию функции лучше, нужно посмотреть на специфику тех аргументов, которые в конкретном месте передаются в эту функцию. То есть, иногда очень полезно получить этот граф реальных вызовов и его проанализировать. В этом случае там уже идет какая-то работа с бинарным кодом и вставляется инструментация. Но самые простые вещи, такие как собрать привязку по времени, там никакой бинарной инструментации нет, там экзешник выпускается так, как он есть. А вот если у вас есть эта программа, и вы хотите еще и посмотреть, на какие строки кода (если вы с ассемблером не очень дружите), вот там вам нужна лай информация, и соответственно, нужно читать документацию компиляторов разных.
Vtune, как вы поняли, работает с любым экзешником, с любым исполняемым файлом, ему совсем не важно, чтобы это был файл, собранный, допустим, компилятором Intel или Visual studio, или еще какими-то другими оперативными компиляторами; ему неважно, какой это язык – С++, С, Фортран. Главное, при этом, есть бинарный код и есть отладочная информация, которая должна удовлетворять каким-то требованиям. Удовлетворяет информация отладочная, которая была подготовлена с помощью этих компиляторов стандартам, соответственно, вы все увидите.
В данном случае нужна привязка от линии кода к ассемблеровским инструкциям. Эта дебаговская информация, в принципе, невредная. То есть если включение отладки программы портит какие-то оптимизации, не дает компилятору в полном объеме оптимизировать, то иметь привязку инструкции к линиям кода — это достаточно безопасная вещь.