Инструменты для улучшения производительности кода C++

Мы (Адриан, Марсель и Тони) создаем магистерский проект «C++ Low-Level Performance Engineering» в Институте Хассо Платтнера. В предыдущем мастер-проекте «Оптимизация плана запросов» было опубликовано семь сообщений в блоге из серии Hyrise. В ближайшие месяцы мы собираемся поделиться некоторыми сообщениями о том, как мы улучшаем производительность Hyrise.

Когда Hyrise разрабатывался в течение последних месяцев, многие из новых функций были запрограммированы с упором на добавление дополнительных функций базы данных. Мы не хотим утверждать, что производительность была проигнорирована. Однако мы видим большой потенциал для улучшения некоторых из недавно добавленных компонентов. Цель нашего магистерского проекта — выявить и понять узкие места в коде. Чтобы исправить этих нарушителей производительности, мы используем инструменты профилирования кода.

Что такое профилирование кода?

Профилировщики кода — это инструменты, которые динамически анализируют поведение программ во время выполнения. Есть много интересных факторов, влияющих на качество и производительность программного обеспечения. Как правило, мы хотим изучить используемые ресурсы (например, использование ЦП, использование основной памяти), продолжительность конкретных методов или поведение параллелизма. Таким образом, мы можем найти узкие места производительности в нашем проекте базы данных. Профилирование помогает нам сосредоточиться на критических путях кода и оптимизировать дорогостоящие части вместо оптимизации последних двух процентов производительности. Мы проводим профилирование кода на оптимизированном для компилятора двоичном файле во время выполнения системы базы данных. Полученная информация о запуске зависит от самого профилировщика. Большинство из них предоставляют статистическую сводку наблюдаемых событий.

Различные инструменты профилирования

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

Коллгринд (Валгринд)

Valgrind — это набор различных инструментов для профилирования и отладки памяти. По сути, это виртуальная машина, которая использует методы компиляции Just-In-Time для динамического анализа программ. Одним из инструментов этой инструментальной среды является Callgrind. Использование этого просто. Просто добавьте вызов valgrind при выполнении бинарного файла программы, и профилирование запустится. Чтобы проанализировать производительность программы, callgrind добавляет к ней инструкции. Следовательно, время выполнения значительно увеличится (возможно, в 100 раз). Инструмент создает файл, содержащий всю проверенную информацию. Поскольку этот файл не в удобочитаемом формате, вы можете использовать инструмент визуализации. В нашем проекте мы используем kcachegrind. Он предоставляет понятный и интерактивный пользовательский интерфейс, включая плоский профиль и график вызовов, как показано на рисунке 1.

По сравнению с другими профилировщиками, использующими выборку, этот метод очень точен, когда речь идет об измерениях вызова метода во время выполнения. Единственным недостатком является медленное время выполнения, как описано выше.

Рисунок 1. Пользовательский интерфейс kcachegrind

Apple инструменты

Apple Instruments является частью набора инструментов Apple Xcode. Apple Instruments предоставляет, среди прочего, профилировщик времени, который измеряет общее время выполнения каждой функции. Эти времена получены путем выборки. Это означает, что он регулярно прерывается и проверяется стек вызовов функций. После выполнения программы можно рассчитать приблизительное время выполнения на основе этих стеков вызовов функций. Графический интерфейс показывает общее время выполнения и общий вес.

Инструмент прост в использовании, имеет понятный пользовательский интерфейс и не требует особых настроек для профилирования программного обеспечения. После выбора исполняемого файла программы можно начать профилирование. Основным недостатком инструментов является зависимость от операционной системы. Он доступен только для macOS. Кроме того, результаты профилирования не так точны, как результаты callgrind. Из-за использования выборки возникают статистические ошибки, искажающие результаты.

Аппаратные счетчики производительности (PAPI)

Современные микропроцессоры имеют специальные регистры, зарезервированные для измерения производительности оборудования. Эти счетчики могут хранить метрики, связанные с аппаратным обеспечением (например, количество циклов, промахи кэша или ошибочные прогнозы переходов). Они находятся в свободном доступе и должны быть инициализированы для определенной метрики. Количество аппаратных счетчиков ограничено и зависит от микропроцессора. API производительности (PAPI) предоставляет интерфейс прикладного программирования для доступа к аппаратным счетчикам производительности. В следующем примере показано, как мы можем использовать PAPI для подсчета определенных событий.

В этом небольшом примере мы подсчитываем общее количество затраченных циклов ЦП и циклов ЦП, ожидающих доступа к памяти (см. строку 7). Подсчет событий будет включен в строке 9 для проверки метода expensive_method(). После возврата метода подсчет событий отключается, а метрики собираются в строке 11. Одно из основных преимуществ PAPI — сравнительно низкие накладные расходы.

Вывод

Мы опробовали несколько инструментов профилирования кода, увидели их преимущества и недостатки. С помощью этих профилировщиков производительности мы хотим найти узкие места, устранить их и, таким образом, сделать Hyrise еще быстрее. Результаты тестов, улучшения и идеи будут опубликованы в наших следующих публикациях.

— Адриан, Марсель и Тони