Содержание


Измерение производительности Linux на компьютерах POWER

Анализ производительности с помощью инструментов Linux

Comments

Оценки производительности приложений на современных компьютерах может оказаться сложной задачей. Стандартные общеизвестные инструменты с трудом справляются со всеми переменными, относящимися к производительности. Любая рабочая нагрузка по-разному влияет на подсистемы, которые она использует. Процесс измерения и настройки программы, «привязанной» к использованию CPU, сильно зависит от настройки программ, ориентированных на работу с вводом/выводом или памятью. В этой статье мы сконцентрируемся на программах, интенсивно использующих процессор и память и работающих в окружениях компилируемых языков (C, C++ и другие). Мы покажем как:

  • найти в программе "горячую точку" (область или функцию в программе, где находится большая часть исполняемых инструкций);
  • измерить, как программа работает на платформе POWER7 с помощью встроенного в процессор аппаратного счётчика производительности;
  • выбрать инструменты, которые используются для оценки производительности в ОС Linux.

CPI-модель для POWER7

Анализ производительности приложения основан на использовании CPI-метрик. Величина CPI (cycles per instruction) определяет количество тактов процессора, необходимых для выполнения инструкции. Каждая инструкция раскладывается на несколько этапов: в классическом RISC-конвейере имеется стадия загрузки инструкции, за которой следует декодирование инструкции и выборка регистров, непосредственное исполнение с доступом к памяти (если это требуется), и, наконец, запись результата. Процессор может улучшить свою CPI-характеристику (т.е. снизить её величину) за счёт внедрения параллелизма на уровне инструкций, когда на каждом этапе могут обрабатываться различные инструкции на разных стадиях. При оптимизации, постарайтесь уменьшить значение CPI, чтобы повысить уровень использования системы. На Рисунок 1 показан оптимальный поток исполнения инструкций для конвейерного процессора.

Рисунок 1. Оптимальный поток инструкций для конвейерного процессора.
Оптимальный поток инструкций для конвейерного процессора.
Оптимальный поток инструкций для конвейерного процессора.

Иногда один этап частично зависит от других этапов выполнения или выполняется инструкция с определенными зависимостями, которые заставляют процессоры выполнить указанные требования, прежде чем исполнение может быть продолжено. Например, обращение к памяти, за которым следует арифметическая инструкция, заставляет процессор сначала загрузить данные в кэш или память, и только после этого выполнить арифметическую инструкцию. Когда это происходит, то говорят, что возникает "останов" (stall) процессора, останавливающий весь конвейер исполнения инструкций. На Рисунок 2 показано, как может выглядеть "замерший" конвейер.

Рисунок 2. "Замерший" конвейерный процессор
Рисунок 2. Замерший конвейерный процессор
Рисунок 2. Замерший конвейерный процессор

В примерах, показанных на рисунках Рисунок 1 и Рисунок 2, предполагается, что за 11 рабочих тактов на полностью заполненном конвейере (где выполнение инструкции занимает 1 такт) процессор может выполнить восемь инструкций. Однако когда возникает "останов" на три такта, то за тоже количество тактов выполняются только 5 инструкций. Потери производительности в данном случае составляет порядка 40%. В зависимости от алгоритма невозможно избежать некоторых "остановов”, однако тщательный анализ поможет определить, как переписать или настроить отдельные фрагменты кода, чтобы устранить "останов”. Более полное и формальное описание реализации конвейерной обработки в современных процессорах и параллелизма на уровне инструкций можно найти в статье "Modern Microprocessors - a 90 minute guide", приведенной в разделе "Ресурсы".

Модель CPI (CBM - CPI Breakdown Model) связывает этапы работы процессора со счётчиками производительности, чтобы показать, какой функциональный модуль CPU приводит к задержкам. CBM-модель зависит от архитектуры и модели процессора, так архитектуры POWER и INTEL используют абсолютно разные CBM-модели, а CBM-модели у POWER5 и POWER7 различаются. На Рисунок 3 изображен фрагмент CBM-модели POWER7, а здесь можно познакомиться с её текстовой версией.

Рисунок 3. Фрагмент CBM-модели для архитектуры POWER7
Фрагмент CBM-модели для архитектуры POWER7
Фрагмент CBM-модели для архитектуры POWER7

В архитектуре POWER аппаратные счётчики производительность представляют собой набор регистров специального назначения, содержимое которых обновляется, когда в процессоре происходит определенное событие. Процессор POWER7 содержит встроенный PMU-модуль (Performance Monitoring Unit – модуль мониторинга производительности) с шестью PCM-блоками (Performance Counter Monitor – счётчики-мониторы производительности), работающими на уровне потоков. Четыре блока из шести являются программируемыми, что позволяет одновременно вести мониторинг четырех событий, а всего существует более 500 событий, связанных с производительностью. Счётчики производительности в POWER7 объединяются в группы, и PMU может одновременно наблюдать только за событиями, относящимися к одной группе. На Рисунок 3 показана часть счётчиков производительности POWER7, используемых для создания CBM-модели POWER7. Счётчики, изображенные на Рисунок 3 работают в соответствии с этим профилем, чтобы определить, какой модуль CPU приводит к "останову" процессора и предоставить рекомендации о том, как необходимо настроить алгоритм для восстановления производительности.

На Рисунок 3 белые прямоугольники – это определенные счётчики производительности POWER7, отслеживаемые в соответствии с профилем. В зависимости от их значений вычисляются значений серых прямоугольников, отмеченных звёздочкой, которые представляют собой метрики без выделенных аппаратных счётчиков производительности.

Примечание: развёрнутое руководство по PMU для POWER7 можно найти в статье "Comprehensive PMU Event Reference POWER7", приведенной в разделе "Ресурсы".

Инструменты для Linux

Как можно использовать модули PCM, существующие в процессорах POWER7? Хотя на архитектуре POWER доступны различные методы для профилирования, включая аппаратные прерывания и контроль выполнения кода (например, инструмент gprof), «хуки» операционной системы (systemtap); но PCM предлагает богатый набор счётчиков, работающих непосредственно с функциональностью процессора. Профилировщик PCM постоянно через определенные интервалы предоставляет значение регистров процессора с помощью прерываний операционной системы. Хотя подобный способ профилирования может давать результаты менее точные, нежели результаты трассировки инструкций, но он меньше влияет на общую производительность системы и позволяет бенчмарк-программе работать фактически на полной скорости. Но полученные данные будут неточными, так как представляют собой аппроксимацию с учётом возможной ошибки.

Для профилирования PCM в Linux чаще всего используются два инструмента: OProfile и perf (см. ссылки в разделе "Ресурсы"). Хотя оба инструмента основаны на одном и том же принципе, постоянно делая выборку из специального аппаратного регистра через syscall вместе с обратным трассированием рабочей нагрузки, но каждый из них конфигурируется и используется различными способами.

Инструмент OProfile – это профайлер системного уровня для Linux, способный выполнять профилирование всего работающего кода с минимальной добавочной нагрузкой. Он состоит из драйвера ядра и демона для сборки проверочных данных, а также нескольких инструментов, которые используются после процесса профилирования для преобразования данных в информацию. Отладочные символы (параметр –g компилятора gcc) не требуются, если вы не хотите работать с размеченным исходным кодом. В свежей версии 2.6 ядра Linux OProfile может предоставлять информацию о результатах профилирования в стиле gprof. Обычно при использовании OProfile нагрузка на систему увеличивается на 1 – 8 %, в зависимости от частоты выборки данных и сложности рабочей нагрузки.

На архитектуре POWER OProfile работает, наблюдая за группами аппаратных и программных счётчиков производительности, хотя различные группы и не могут использоваться вместе. Поэтому, чтобы получить значения различных счётчиков производительности для одной и той же рабочей нагрузки, приходится выполнять её несколько раз с различной конфигурацией событий OProfile. Это также значит, что нельзя наблюдать за всей CBM-моделью в один момент времени. Доступные группы определены в уже упоминавшемся документе “POWER7 PMY Detailed Event Description”, также их список можно получить с помощью команды, приведенной в Листинг 1.

Листинг 1. Вывод групп OProfile
 # opcontrol -l

В Листинг 2 дается пример команд для конфигурации и запуска OProfile.

Листинг 2. Конфигурация OProfile для отслеживания тактов CPU на платформе POWER7
 # opcontrol -l # opcontrol -–no-vmlinux # opcontrol -e PM_CYC_GRP1:500000 -e PM_INST_CMPL_GRP1:500000 -e PM_RUN_CYC_GRP1:500000 -e PM_RUN_INST_CMPL_GRP1:500000 # opcontrol --start

В Листинг 3 запускается рабочая нагрузка и собирается информация с помощью OProfile.

Листинг 3. Последовательность команд для запуска OProfile
 # opcontrol --dump # opcontrol –-stop # opcontrol --shutdown

Чтобы получить отчёт счётчика производительности, воспользуйтесь командой из Листинг 4.

Листинг 4. Генерация отчёта OProfile
 # opreport -l > workload_report

Примечание: подробное руководство по OProfile (хотя и не обновленное для POWER7) можно найти в статье "Identify performance bottlenecks with OProfile for Linux on POWER", опубликованной на портале developerWorks (см. раздел "Ресурсы").

Инструмент perf, представленный в версии 2.6.29 ядра Linux, анализирует события, связанные с производительностью, на аппаратном и программном уровне. Преимуществом данного инструмента является возможность использования на уровне программ, а не на уровне всей системы, как Oprofile. Он также поддерживает настраиваемые списки счётчиков производительности, например, 'cpu-cycles OR cycles', 'branch-misses' или 'L1-icache-prefetch-misses', а также умеет объёдинять группы PMU для одновременного сбора информации со счётчиков производительности, относящихся к различным группам, но за счёт некоторой потери точности измерения.

Негативным фактором, связанным с perf, является то, что хотя этот инструмент позволяет собирать информацию с аппаратных счётчиков производительности напрямую, но он не распознаёт имена счётчиков, объявленные в CBM-модели POWER7, а использует вместо них оригинальные шестнадцатеричные значения. В Таблица 1 представлено соответствие между событиями OProfile и шестнадцатеричными значениями, используемыми в perf (с помощью опции для вывода необработанных записей о событиях), который можно использовать для привязки к CBM-модели POWER7.

Таблица 1. Исходные коды событий для архитектуры POWER7
СчётчикКод
PM_RUN_CYC200f4
PM_CMPLU_STALL 4000a
PM_CMPLU_STALL_FXU20014
PM_CMPLU_STALL_DIV40014
PM_CMPLU_STALL_SCALAR40012
PM_CMPLU_STALL_SCALAR_LONG20018
PM_CMPLU_STALL_VECTOR2001c
PM_CMPLU_STALL_VECTOR_LONG4004a
PM_CMPLU_STALL_LSU20012
PM_CMPLU_STALL_REJECT40016
PM_CMPLU_STALL_ERAT_MISS40018
PM_CMPLU_STALL_DCACHE_MISS20016
PM_CMPLU_STALL_STORE2004a
PM_CMPLU_STALL_THRD1001c
PM_CMPLU_STALL_IFU4004c
PM_CMPLU_STALL_BRU4004e
PM_GCT_NOSLOT_CYC100f8
PM_GCT_NOSLOT_IC_MISS2001a
PM_GCT_NOSLOT_BR_MPRED4001a
PM_GCT_NOSLOT_BR_MPRED_IC_MISS4001c
PM_GRP_CMPL30004
PM_1PLUS_PPC_CMPL100f2

Примечание: подробное руководство по perf (хотя и не обновленное для POWER7) можно найти в статье "Using perf on POWER7 systems", опубликованной на IBM Wiki (см. раздел "Ресурсы").

Привязать оригинальные коды, используемые perf, к событиям POWER7, определенным в OProfile, можно также с помощью проекта libpfm4 (см. раздел "Ресурсы"). Эти коды определены в заголовочном файле для POWER7 – lib/events/power7_events.h. В файле примера examples/showevtinfo также приведены имена событий и соответствующие им шестнадцатеричные коды.

Профилирование является стандартным способом сбора информации со счётчиков производительности, который также позволяет разработчикам идентифицировать в коде наиболее часто исполняемые места и места с интенсивным обращением к данным. Также профилирование помогает определить в коде участки, влияющие на производительность, выявить шаблоны обращения к памяти и т.д. Но прежде чем начать профилирование приложения, необходимо определить стратегию оценки производительности. Программа может состоять из различных модулей и/или динамических объектов совместного доступа (DSO - dynamic shared object); она может интенсивно использовать возможности ядра, зависеть от шаблона доступа к данным (например, при интенсивном обращении к L2 или L3-кэшу) или сконцентрировать нагрузку на модулях, выполняющих векторные операции. В следующем разделе мы расскажем о возможных стратегиях для оценки производительности.

Стратегии оценки производительности

Первоначальная оценка производительности сводится к поиску "горячих точек" программы изучением счетчика утилизации циклов CPU. Для этого на архитектуре POWER7 необходимо пронаблюдать за событиями, перечисленными в Таблица 2.

Таблица 2. Счётчики утилизации тактов CPU для архитектуры POWER7
СчётчикиОписание
PM_CYCКоличество тактов процессора
PM_INST_CMPLКоличество выполненных инструкций PowerPC
PM_RUN_CYCКоличество циклов процессора, прошедших через latch-регистр (англ. latch – регистр-защёлка). Операционные системы используют этот регистр, чтобы показать, что они выполняют полезную работу. В цикле простоя ОС latch-регистр обычно очищается, поэтому, отфильтровав такты процессора по состоянию этого регистра, можно отбросить те такты, когда CPU находился в режиме простоя.
PM_RUN_INST_CMPLКоличество выполненных полезных инструкций.

Запустив OProfile для отслеживания этих событий, можно получить информацию о полном времени, потраченном процессором, в необработанном виде. В листинге 5 представлен результат вывода профайлера для бенчмарк-компонента 403.gcc из тестового набора SPECcpu2006, скомпилированного с помощью IBM Advance Toolchain 5.0 for POWER (см. ссылку в разделе "Ресурсы"). Представленный вывод был получен с помощью команды opreport –l.

Листинг 5. Вывод команды opreport –l для бенчмарк-компонента 403.gcc (выведены значения счётчиков PM_CYC_GRP1 и PM_INST_CMPL_GRP1)
 CPU: ppc64 POWER7, speed 3550 MHz (estimated) Counted PM_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles) with a unit mask of 0x00 (No unit mask) count 500000 Counted PM_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of PowerPC Instructions that completed.) with a unit mask of 0x00 (No unit mask) count 500000 samples % samples % image name app name symbol name 204528 7.9112 32132 1.3848 gcc_base.none gcc_base.none reg_is_remote_cons\ tant_p.isra.3.part.4 125218 4.8434 246710 10.6324 gcc_base.none gcc_base.none bitmap_operation 113190 4.3782 50950 2.1958 libc-2.13.so libc-2.13.so memset 90316 3.4934 22193 0.9564 gcc_base.none gcc_base.none compute_transp 89978 3.4804 11753 0.5065 vmlinux vmlinux .pseries_dedicated_\ idle_sleep 88429 3.4204 130166 5.6097 gcc_base.none gcc_base.none bitmap_element_\ allocate 67720 2.6194 41479 1.7876 gcc_base.none gcc_base.none ggc_set_mark 56613 2.1898 89418 3.8536 gcc_base.none gcc_base.none canon_rtx 53949 2.0868 6985 0.3010 gcc_base.none gcc_base.none delete_null_\ pointer_checks 51587 1.9954 26000 1.1205 gcc_base.none gcc_base.none ggc_mark_rtx_\ children_1 48050 1.8586 16086 0.6933 gcc_base.none gcc_base.none single_set_2 47115 1.8224 33772 1.4555 gcc_base.none gcc_base.none note_stores
Листинг 6. Вывод команды opreport –l для бенчмарк-компонента 403.gcc (выведены значения счётчиков PM_RUN_CYC_GRP1 и PM_RUN_INST_CMPL_GRP1)
 Counted PM_RUN_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles gated by the run latch. Operating systems use the run latch to indicate when they are doing useful work. The run latch is typically cleared in the OS idle loop. Gating by the run latch filters out the idle loop.) with a unit mask of 0x00 (No unit mask) count 500000 Counted PM_RUN_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of run instructions completed.) with a unit mask of 0x00 (No unit mask) count 500000 samples % samples % samples % app name symbol name 204538 8.3658 32078 1.3965 gcc_base.none gcc_base.none reg_is_remote_consta\ nt_p.isra.3.part.4 124596 5.0961 252227 10.9809 gcc_base.none gcc_base.none bitmap_operation 112326 4.5943 50890 2.2155 libc-2.13.so libc-2.13.so memset 90312 3.6939 21882 0.9527 gcc_base.none gcc_base.none compute_transp 0 0 0 0 vmlinux vmlinux .pseries_dedicated\ _idle_sleep 88894 3.6359 124831 5.4346 gcc_base.none gcc_base.none bitmap_element_all\ ocate 67995 2.7811 41331 1.7994 gcc_base.none gcc_base.none ggc_set_mark 56460 2.3093 89484 3.8958 gcc_base.none gcc_base.none canon_rtx 54076 2.2118 6965 0.3032 gcc_base.none gcc_base.none delete_null_pointer\ _checks 51228 2.0953 26057 1.1344 gcc_base.none gcc_base.none ggc_mark_rtx_childr\ en_1 48057 1.9656 16005 0.6968 gcc_base.none gcc_base.none single_set_2 47160 1.9289 33766 1.4700 gcc_base.none gcc_base.none note_stores

Каждое отслеживаемое событие представлено в выводимой информации двумя столбцами. В первом столбце показывается количество обращений к счётчику PCM, относящемуся к данному событию, а во втором столбце показывается, какое количество процентов от общего числа обращений занимает первое значение. Как видно по данному отчёту, символ reg_is_remote_constant_p занимает первое место по количеству использованных тактов процессора, и поэтому является подходящим кандидатом для оптимизации. Данный тип профилирования позволяет только выявить, какие символы занимают больше всего тактов процессора, но не позволяет определить, насколько полно загружен конвейер процессора. Определить степень утилизации конвейера можно, если сравнить показания счётчиков.

Рассмотрим счётчик PM_INST_CMPL_GRP1 (вторая пара столбцов), его процентное значение для символа bitmap_operation оказывается выше, чем для символа reg_is_remote_constant_p. Этот счетчик производительности увеличивается на единицу каждый раз, когда процессор завершает выполнение инструкции, тогда как счётчик PM_CYC_GRP1 измеряет только количество использованных тактов процессора. Даже без дополнительного анализа это соотношение может показать, что символ reg_is_remote_constant_p сильнее "тормозит" процессор, нежели символ bitmap_operation, так как количество инструкций, выполненных процессором для символа reg_is_remote_constant_p меньше, чем количество инструкций, выполненных для символа bitmap_operation. Данное исследование указывает направление, в котором следует сфокусировать дальнейшие усилия по оптимизации.

Прежде, чем вы влезать глубже и разбирать код, будет полезно понять, с чем именно в основном взаимодействует рабочая нагрузка: с CPU или с памятью? Это важно, так как для каждого типа нагрузки подходы к оптимизации значительно отличаются. Например, чаще всего доступ к памяти осуществляется из кэша или основной памяти (в противоположность доступу к памяти удаленного хоста в технологии NUMA), и производительность в основном зависит от используемых структур данных и алгоритмов. Чтобы исследовать шаблоны доступа к памяти, применяемые в рабочей нагрузке, необходимо пронаблюдать за двумя счётчиками производительности, указанными в Таблица 3.

Таблица 3. Счётчики утилизации памяти в архитектуре POWER7
СчётчикОписание
PM_MEM0_RQ_DISPЗапросы на чтение из основной памяти
PM_MEM0_WQ_DISPЗапросы на запись в основную память

Эти два счётчика могут показать, из каких операций в основном состоит шаблон доступа к памяти: из чтения, записи или и чтения и записи одновременно. Воспользуемся уже знакомым бенчмарком 403.gcc из пакета SPECcpu2006 и провёдем профилирование нагрузки.

Листинг 7. Вывод команды opreport –l для бенчмарк-компонента 403.gcc (выведены значения счётчиков PM_MEM0_RQ_DISP и PM_MEM0_WQ_DISP)
 CPU: ppc64 POWER7, speed 3550 MHz (estimated) Counted PM_MEM0_RQ_DISP_GRP59 events ((Group 59 pm_nest2) Nest events (MC0/MC1/PB/GX), Pair0 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 Counted PM_MEM0_WQ_DISP_GRP59 events ((Group 59 pm_nest2) Nest events (MC0/MC1/PB/GX), Pair3 Bit1) with a unit mask of 0x00 (No unit mask) count 1000 samples % samples % app name symbol name 225841 25.8000 289 0.4086 gcc_base.none reg_is_remote_constant_p.\ isra.3.part.4 90068 10.2893 2183 3.0862 gcc_base.none compute_transp 54038 6.1733 308 0.4354 gcc_base.none single_set_2 32660 3.7311 2006 2.8359 gcc_base.none delete_null_pointer_checks 26352 3.0104 1498 2.1178 gcc_base.none note_stores 21306 2.4340 1950 2.7568 vmlinux .pseries_dedicated_idle_sl\ eep 18059 2.0631 9186 12.9865 libc-2.13.so memset 15867 1.8126 659 0.9316 gcc_base.none init_alias_analysis

Также полезным могут оказаться счётчики производительности, связанные с доступом к кэшу, точнее к обоим кэшам – L2 и L3. В примере, приведенном ниже, используется инструмент perf для профилирования компонента 483.xalancbmk, собранного с помощью компилятора RHEL6.2 Linux system GCC, из пакета SPECcpu2006 (см. раздел "Ресурсы"). Этот компонент интенсивно использует процедуры выделения памяти, так что можно ожидать значительной нагрузки на подсистему памяти. Чтобы проверить это, будем следить за счётчиками, перечисленными в Таблица 4, с помощью OProfile.

Таблица 4. Счётчики, измеряющие доступ к памяти/кэшу в архитектуре POWER7
СчётчикОписание
PM_DATA_FROM_L2Кэш данных процессора был перезагружен из локального кэша L2 из-за запроса на загрузку данных.
PM_DATA_FROM_L3Кэш данных процессора был перезагружен из локального кэша L3 из-за запроса на загрузку данных.
PM_DATA_FROM_LMEMКэш данных процессора был перезагружен из памяти, относящейся к тому же модулю, где находится сам процессор.
PM_DATA_FROM_RMEMКэш данных процессора был перезагружен из памяти, относящейся к другому модулю, а не к тому, где находится сам процессор.

В Листинг 8 приведен результат профилирования.

Листинг 8. Вывод команды opreport –l для бенчмарк-компонента 489.Xalancbmk (выведены значения счётчиков PM_DATA_FROM_L2_GRP91 и PM_DATA_FROM_L3_GRP91)
 CPU: ppc64 POWER7, speed 3550 MHz (estimated) Counted PM_DATA_FROM_L2_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache was reloaded from the local L2 due to a demand load.) with a unit mask of 0x00 (No unit mask) count 1000 Counted PM_DATA_FROM_L3_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache was reloaded from the local L3 due to a demand load.) with a unit mask of 0x00 (No unit mask) count 1000 samples % samples % image name app name symbol name 767827 25.5750 7581 0.2525 gcc_base.none gcc_base.none bitmap_element_allocate 377138 12.5618 8341 0.2778 gcc_base.none gcc_base.none bitmap_operation 93334 3.1088 3160 0.1052 gcc_base.none gcc_base.none bitmap_bit_p 70278 2.3408 5913 0.1969 libc-2.13.so libc-2.13.so _int_free 56851 1.8936 22874 0.7618 oprofile oprofile /oprofile 47570 1.5845 2881 0.0959 gcc_base.none gcc_base.none rehash_using_reg 41441 1.3803 8532 0.2841 libc-2.13.so libc-2.13.so _int_malloc
Листинг 9. Вывод команды opreport –l для бенчмарк-компонента 489.Xalancbmk (выведены значения счётчиков PM_DATA_FROM_LMEM_GRP91 и PM_DATA_FROM_RMEM_GRP91)
 Counted PM_DATA_FROM_LMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache was reloaded from memory attached to the same module this proccessor is located on.) with a unit mask of 0x00 (No unit mask) count 1000 Counted PM_DATA_FROM_RMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache was reloaded from memory attached to a different module than this proccessor is located on.) with a unit mask of 0x00 (No unit mask) count 1000 samples % samples % image name app name symbol name 1605 0.3344 0 0 gcc_base.none gcc_base.none bitmap_element_allocate 1778 0.3704 0 0 gcc_base.none gcc_base.none bitmap_operation 1231 0.2564 0 0 gcc_base.none gcc_base.none bitmap_bit_p 205 0.0427 0 0 libc-2.13.so libc-2.13.so _int_free 583 0.1215 327 100.000 oprofile oprofile /oprofile 0 0 0 0 gcc_base.none gcc_base.none rehash_using_reg 225 0.0469 0 0 libc-2.13.so libc-2.13.so _int_malloc

Анализ результатов профилирования показывает, что основная нагрузка на кэш процессора связана с доступом из кэша L2, в то время как кэш L3 практически не требует перезагрузки. Общее и относительно значение счётчика, отслеживающего доступ к кэшу L2 (PM_DATA_FROM_L2) значительно выше, чем количество запросов на перезагрузку со стороны кэша L3 (PM_DATA_FROM_L2). Можно продолжить наблюдение за другими счётчиками, чтобы получить дополнительную информацию и узнать, например, не вызывает ли доступ к кэшу L2 "остановов" в работе процессора из-за непредвиденных обращений к кэшу. По результатам профилирования данного примера можно сделать заключение, что доступ к основной памяти (событие PM_DATA_FROM_LMEM) довольно мал по сравнению с доступом к кэшу, а также то, что удалённый доступ к памяти по технологии NUMA (событие PM_DATA_FROM_RMEM) отсутствует. Анализ «горячих точек» программы и шаблонов доступа к памяти может указать направления, куда следует приложить усилия по оптимизации. В данном случае требуется дальнейший анализ, чтобы определить, что конкретно вызывает "остановы" CPU, так как идентификации "горячих точек" и шаблонов доступа к памяти, присутствующих в рабочей нагрузке недостаточно для точного определения причины "простаивания" CPU.

Чтобы выработать лучшую стратегию для оптимизации производительности, дальнейший анализ потребует использования инструмента perf, а не OProfile, так как многие CBM-счётчики в архитектуре POWER7 должны наблюдаться одновременно (например, 22 счётчика, представленные на Рисунок 3). Многие из этих событий находятся в различных группах, поэтому использование OProfile для наблюдения за ними потребует нескольких запусков рабочей нагрузки. Инструмент perf может одновременно наблюдать за аппаратными счётчиками, даже если они находятся в нескольких группах. Хотя это и снижает точность, но общие результаты обоих подходов в целом совпадают, кроме того, использование perf позволяет сократить количество времени необходимого на выполнение профилирования.

В следующем примере инструмент perf используется для профилирования компонента 483.xalancbmk из пакета SPECcpu2006. Чтобы запустить процесс профилирования, воспользуйтесь командой из Листинг 10.

Листинг 10. Запуск команды perf для наблюдения за CBM-счётчиками POWER7
 $ /usr/bin/perf stat -C 0 -e r100f2,r4001a,r100f8,r4001c,r2001a,r200f4,r2004a,r4004a, r4004e,r4004c,r20016,r40018,r20012,r40016,r40012,r20018,r4000a,r2001c,r1001c,r20014, r40014,r30004 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl > power7_cbm.dat

Эта команда заставляет perf наблюдать за оригинальными событиями, определенными в опции -e, на процессоре, определенном в опции . Системный вызов taskset гарантирует, что компонент будет работать исключительно на процессоре с номером 0. Файл с рабочей нагрузкой ./Xalan_base.none -v t5.xml xalanc.xsl можно заменить на другое приложение, которое требуется подвергнуть профилированию. После того как профилирование будет завершено, команда perf выведет таблицу с общим количеством срабатываний каждого отслеживаемого события и количеством секунд, потраченных на выполнение работы.

Листинг 11. Вывод команды 'perf stat' для бенчмарк-компонента 489.Xalancbmk
 Performance counter stats for 'taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl': 366,860,486,404 r100f2 [18.15%] 8,090,500,758 r4001a [13.65%] 50,655,176,004 r100f8 [ 9.13%] 11,358,043,420 r4001c [ 9.11%] 10,318,533,758 r2001a [13.68%] 1,301,183,175,870 r200f4 [18.22%] 2,150,935,303 r2004a [ 9.10%] 0 r4004a [13.65%] 211,224,577,427 r4004e [ 4.54%] 212,033,138,844 r4004c [ 4.54%] 264,721,636,705 r20016 [ 9.09%] 22,176,093,590 r40018 [ 9.11%] 510,728,741,936 r20012 [ 9.10%] 39,823,575,049 r40016 [ 9.07%] 7,219,335,816 r40012 [ 4.54%] 1,585,358 r20018 [ 9.08%] 882,639,601,431 r4000a [ 9.08%] 1,219,039,175 r2001c [ 9.08%] 3,107,304 r1001c [13.62%] 120,319,547,023 r20014 [ 9.09%] 50,684,413,751 r40014 [13.62%] 366,940,826,307 r30004 [18.16%] 461.057870036 seconds time elapsed

Для анализа вывода инструмента perf для CBM-модели для POWER7 можно воспользоваться сценарием на Python, который можно найти в архиве power7_cbm.zip в разделе "Материалы для скачивания", объединяющем метрики, собранные с виртуальных и аппаратных счётчиков. Для создания отчёта воспользуйтесь командной приведенной в Листинг 12.

Листинг 12. Вызов python-сценария для создания отчёта по счётчикам CBM-модели POWER7
 $ power7_cbm.py power7_cbm.dat

В результате будет выведена информация, аналогичная приведенной в Листинг 13.

Листинг 13. Вывод сценария power7_cbm.py для бенчмарк-компонента 489.Xalancbmk
 CPI Breakdown Model (Complete) Metric : Value : Percent PM_CMPLU_STALL_DIV : 49802421337.0 : 0.0 PM_CMPLU_STALL_FXU_OTHER : 67578558649.0 : 5.2 PM_CMPLU_STALL_SCALAR_LONG : 2011413.0 : 0.0 PM_CMPLU_STALL_SCALAR_OTHER : 7195240404.0 : 0.6 PM_CMPLU_STALL_VECTOR_LONG : 0.0 : 0.0 PM_CMPLU_STALL_VECTOR_OTHER : 1209603592.0 : 0.1 PM_CMPLU_STALL_ERAT_MISS : 22193968056.0 : 1.7 PM_CMPLU_STALL_REJECT_OTHER : 18190293594.0 : 1.4 PM_CMPLU_STALL_DCACHE_MISS : 261865838255.0 : 20.3 PM_CMPLU_STALL_STORE : 2001544985.0 : 0.2 PM_CMPLU_STALL_LSU_OTHER : 202313206181.0 : 15.7 PM_CMPLU_STALL_THRD : 2025705.0 : 0.0 PM_CMPLU_STALL_BRU : 208356542821.0 : 16.2 PM_CMPLU_STALL_IFU_OTHER : 2171796336.0 : 0.2 PM_CMPLU_STALL_OTHER : 30895294057.0 : 2.4 PM_GCT_NOSLOT_IC_MISS : 9805421042.0 : 0.8 PM_GCT_NOSLOT_BR_MPRED : 7823508357.0 : 0.6 PM_GCT_NOSLOT_BR_MPRED_IC_MISS : 11059314150.0 : 0.9 PM_GCT_EMPTY_OTHER : 20292049774.0 : 1.6 PM_1PLUS_PPC_CMPL : 365158978504.0 : 28.3 OVERHEAD_EXPANSION : 590057044.0 : 0.0 Total : 96.1

Этот отчёт основан на статистических значениях с учетом возможной погрешности, так что окончательные процентные значения не являются точными на 100%. Но даже с учетом высокой погрешности, около 20% задержек в работе CPU связанно с ошибочными обращениями к кэшу данных (PM_CMPLU_STALL_DCACHE_MISS). Окончательный процент выполнения инструкций (PM_1PLUS_PPC_CMPL) составляет около 28%.

В будущем для оптимизации стоит постараться увеличить это значение, уменьшив процент количества остановов CPU и/или GCT (Global Completion Table — глобальной таблицы исполнения). На базе анализа этого отчёта также можно найти в коде программы места, где происходят остановы CPU. Для этого можно воспользоваться командой perf record, отслеживающую с помощью счётчика "необработанную" производительность и создающую карту выполнения процесса, которая позволяет идентифицировать, какой символ процесса сгенерировал больше всего аппаратных событий. Это похоже на то, как работает OProfile. В Листинг 14 приведена команда для отслеживания событий типа PM_CMPLU_STALL_DCACHE_MISS.

Листинг 14. Команда perf record для отслеживания события PM_CMPLU_STALL_DCACHE_MISS
 $ /usr/bin/perf record -C 0 -e r20016 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl

Команда perf обычно создаёт файл с результатами (который обычно называется "perf.dat"). Его можно считать в интерактивном режиме с помощью команды perf report, как показано в Листинг 15.

Листинг 15. Вывод команды perf report для бенчмарк-компонента 489.Xalancbmk
 Events: 192 raw 0x20016 39.58% Xalan_base.none Xalan_base.none [.] xercesc_2_5::ValueStore::contains 11.46% Xalan_base.none Xalan_base.none [.] xalanc_1_8::XStringCachedAllocator 9.90% Xalan_base.none Xalan_base.none [.] xalanc_1_8::XStringCachedAllocator 7.29% Xalan_base.none Xalan_base.none [.] xercesc_2_5::ValueStore::isDuplica 5.21% Xalan_base.none libc-2.13.so [.] _int_malloc 5.21% Xalan_base.none Xalan_base.none [.] __gnu_cxx::__normal_iterator<xa 4.17% Xalan_base.none libc-2.13.so [.] __GI___libc_malloc 2.08% Xalan_base.none libc-2.13.so [.] malloc_consolidate.part.4 1.56% Xalan_base.none Xalan_base.none [.] xalanc_1_8::ReusableArenaBlock<xa 1.56% Xalan_base.none Xalan_base.none [.] xalanc_1_8::ReusableArenaBlock<xa 1.04% Xalan_base.none libc-2.13.so [.] __free [...]

Данное исследование, проведенное с помощью счётчика CBM-модели POWER7 и отчёта инструмента perf, показывает, что усилия можно сконцентрировать на оптимизации доступа к памяти и кэшу в символе xercesc_2_5::ValueStore::contains(xercesc_2_5::FieldValueMap const*).

Это пример демонстрирует только один из возможных подходов к выполнению анализа. CBM-модель POWER7 показывает, что хотя задержки при обращении к кешу являются причиной большей части остановов CPU, модуль загрузки и хранения (PM_CMPLU_STALL_LSU) и модуль ветвления (PM_CMPLU_STALL_BRU) также тормозят работу CPU. Поэтому дальнейшие анализ может быть направлен на изучение этих счётчиков.

Практический пример

В следующем примере мы на практике применим все эти стратегии для оценки производительности реализации тригонометрической функции. Основываясь на результатах анализа, мы сможем выделить направления для оптимизации. В данном примере используется функция hypot из стандарта ISO С, вычисляющая длину гипотенузы прямоугольного треугольника. В стандарте C99, POSIX.1-2001 эта функция определена как:

double hypot(double x, double y);
Функция hypot() возвращает sqrt(x*x+y*y). В случае успешного вызова функция возвращает длину гипотенузы прямоугольного треугольника со сторонами длиной x и y. Если x или y равны бесконечности, то возвращается бесконечность (Infinity) со знаком плюс. Если один из аргументов не является числом (NaN – not a number), а другой аргумент не равен бесконечности, то возвращается NaN-значение. Если величина результата превышает размерность типа данных, то произойдет ошибка диапазона числа, и функция возвращает одно из возможных значений: HUGE_VAL, HUGE_VALF или HUGE_VALL. Если оба аргумента являются экспоненциальными величинами и результат тоже будет экспоненциальным, то произойдет ошибка диапазона числа, и будет возвращено правильное значение.

Хотя этот алгоритм кажется простым, но обработка аргументов с плавающей точкой (floating point) в случае с Infinity или NaN-значениями или превышение размерности / потеря разрядности, связанные с действиями над значениями с плавающей точкой, оказывают определенное влияние на производительность. В библиотеке GNU C (см. ссылку в разделе "Ресурсы") приведена реализация функции на языке C, которую можно найти в дереве исходного кода в файле sysdeps/ieee754/dbl-64/e_hypot.c.

Примечание: Информация о лицензии для данного фрагмента кода приведена в приложении.

Листинг 16. Стандартная реализация функции hypot() в GLIBC
 double __ieee754_hypot(double x, double y) { double a,b,t1,t2,y1,y2,w; int32_t j,k,ha,hb; GET_HIGH_WORD(ha,x); ha &= 0x7fffffff; GET_HIGH_WORD(hb,y); hb &= 0x7fffffff; if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} SET_HIGH_WORD(a,ha); /* a <- |a| */ SET_HIGH_WORD(b,hb); /* b <- |b| */ if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ k=0; if(ha > 0x5f300000) { /* a>2**500 */ if(ha >= 0x7ff00000) { /* Inf or NaN */ u_int32_t low; w = a+b; /* for sNaN */ GET_LOW_WORD(low,a); if(((ha&0xfffff)|low)==0) w = a; GET_LOW_WORD(low,b); if(((hb^0x7ff00000)|low)==0) w = b; return w; } /* scale a and b by 2**-600 */ ha -= 0x25800000; hb -= 0x25800000; k += 600; SET_HIGH_WORD(a,ha); SET_HIGH_WORD(b,hb); } if(hb < 0x20b00000) { /* b < 2**-500 */ if(hb <= 0x000fffff) { /* subnormal b or 0 */ u_int32_t low; GET_LOW_WORD(low,b); if((hb|low)==0) return a; t1=0; SET_HIGH_WORD(t1,0x7fd00000); /* t1=2^1022 */ b *= t1; a *= t1; k -= 1022; } else { /* scale a and b by 2^600 */ ha += 0x25800000; /* a *= 2^600 */ hb += 0x25800000; /* b *= 2^600 */ k -= 600; SET_HIGH_WORD(a,ha); SET_HIGH_WORD(b,hb); } } /* medium size a and b */ w = a-b; if (w>b) { t1 = 0; SET_HIGH_WORD(t1,ha); t2 = a-t1; w = __ieee754_sqrt(t1*t1-(b*(-b)-t2*(a+t1))); } else { a = a+a; y1 = 0; SET_HIGH_WORD(y1,hb); y2 = b - y1; t1 = 0; SET_HIGH_WORD(t1,ha+0x00100000); t2 = a - t1; w = __ieee754_sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); } if(k!=0) { u_int32_t high; t1 = 1.0; GET_HIGH_WORD(high,t1); SET_HIGH_WORD(t1,high+(k<<20)); return t1*w; } else return w; }

Представленная реализация довольно сложна в основном из-за того, что алгоритм выполняет много побитовых преобразований значений с плавающей точкой в целочисленные значения. Считается, что определенные FP-операции требуют больше ресурсов при использовании инструкций с плавающей точкой, чем при использовании ресурсов с фиксированной точкой. Это справедливо для некоторых архитектур, но не для POWER .

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

Так как данный анализ производительности выполняется только для одной функции, то можно пропустить этап выявления "горячих точек" программы и сфокусироваться на анализе CBM-модели. Воспользуемся бенчмарк-программой hypot_bench.c вместе с утилитой perf, чтобы получить информацию о CBM-модели, приведенную в Листинг 17.

Листинг 17. Вывод сценария power7_cbm.py для бенчмарк-программы hypot
 CPI Breakdown Model (Complete) Metric : Value : Percent PM_CMPLU_STALL_DIV : 8921688.0 : 8.7 PM_CMPLU_STALL_FXU_OTHER : 13953382275.0 : 5.0 PM_CMPLU_STALL_SCALAR_LONG : 24380128688.0 : 8.7 PM_CMPLU_STALL_SCALAR_OTHER : 33862492798.0 : 12.0 PM_CMPLU_STALL_VECTOR_LONG : 0.0 : 0.0 PM_CMPLU_STALL_VECTOR_OTHER : 275057010.0 : 0.1 PM_CMPLU_STALL_ERAT_MISS : 173439.0 : 0.0 PM_CMPLU_STALL_REJECT_OTHER : 902838.0 : 0.0 PM_CMPLU_STALL_DCACHE_MISS : 15200163.0 : 0.0 PM_CMPLU_STALL_STORE : 1837414.0 : 0.0 PM_CMPLU_STALL_LSU_OTHER : 94866270200.0 : 33.7 PM_CMPLU_STALL_THRD : 569036.0 : 0.0 PM_CMPLU_STALL_BRU : 10470012464.0 : 3.7 PM_CMPLU_STALL_IFU_OTHER : -73357562.0 : 0.0 PM_CMPLU_STALL_OTHER : 7140295432.0 : 2.5 PM_GCT_NOSLOT_IC_MISS : 3586554.0 : 0.0 PM_GCT_NOSLOT_BR_MPRED : 1008950510.0 : 0.4 PM_GCT_NOSLOT_BR_MPRED_IC_MISS : 795943.0 : 0.0 PM_GCT_EMPTY_OTHER : 42488384303.0 : 15.1 PM_1PLUS_PPC_CMPL : 53138626513.0 : 18.9 OVERHEAD_EXPANSION : 30852715.0 : 0.0 Total : 108.7

Результаты профилирования показывают, что большинство остановов CPU и, следовательно, потерь производительности связаны со счётчиком PM_CMPLU_STALL_LSU_OTHER для модуля LSU (Load and Store Unit – модуль загрузки и хранения). Хотя к LSU-модулю относится несколько счётчиков, но во время анализа причин замедления CPU мы исследуем только те счётчики, которые связаны с потерями производительности. Те из них, которые демонстрируют снижение производительности на платформе POWER, связаны с LHS-ошибками (Load-Hit-Store). Это существенная задержка, возникающая, когда CPU записывает данные по определённому адресу и практически сразу же после этого пытается загрузить их обратно. На следующем этапе необходимо убедиться, действительно ли эта ошибка присутствует в нашем алгоритме, для чего следует проверить событие PM_LSU_REJECT_LHS (его оригинальный код - "rc8ac"), как показано в Листинг 18.

Листинг 18. Отчёт команды perf record по событию PM_LSU_REJECT_LHS
 $ perf record -C 0 -e rc8ac taskset -c 0 ./hypot_bench_glibc $ perf report Events: 14K raw 0xc8ac 79.19% hypot_bench_gli libm-2.12.so [.] __ieee754_hypot 10.38% hypot_bench_gli libm-2.12.so [.] __hypot 6.34% hypot_bench_gli libm-2.12.so [.] __GI___finite

По результатам профилирования видно, что символ __ieee754_hypot генерирует больше всего событий PM_LSU_REJECT_LHS. Исследуем код сборки, подготовленный компилятором, чтобы определить какие инструкции приводят к возникновению события. Развернём символ __ieee754_hypot для более подробного анализа и просмотрим отчёт утилиты perf, чтобы найти информацию по символу __ieee754_hypot, которая показана в Листинг 19.

Листинг 19. Отчёт команды perf record по событию PM_LSU_REJECT_LHS
 : 00000080fc38b730 <.__ieee754_hypot>: 0.00 : 80fc38b730: 7c 08 02 a6 mflr r0 0.00 : 80fc38b734: fb c1 ff f0 std r30,-16(r1) 0.00 : 80fc38b738: fb e1 ff f8 std r31,-8(r1) 13.62 : 80fc38b73c: f8 01 00 10 std r0,16(r1) 0.00 : 80fc38b740: f8 21 ff 71 stdu r1,-144(r1) 10.82 : 80fc38b744: d8 21 00 70 stfd f1,112(r1) 0.23 : 80fc38b748: e9 21 00 70 ld r9,112(r1) 17.54 : 80fc38b74c: d8 41 00 70 stfd f2,112(r1) 0.00 : 80fc38b750: 79 29 00 62 rldicl r9,r9,32,33 0.00 : 80fc38b754: e9 61 00 70 ld r11,112(r1) 0.00 : 80fc38b758: e8 01 00 70 ld r0,112(r1) 8.46 : 80fc38b75c: d8 21 00 70 stfd f1,112(r1) [...]

Ранее в исходном коде для реализации функции был использован макрос GET_HIGH_WORD, преобразующий значение с запятой в целочисленное значение для последующих побитовых операций. В заголовочном классе GLIBC math/math_private.h этот макрос определен, как показано в Листинг 20.

Листинг 20. Определение макроса GET_HIGH_WORD
 #define GET_HIGH_WORD(i,d) \ do { \ ieee_double_shape_type gh_u; \ gh_u.value = (d); \ (i) = gh_u.parts.msw; \ } while (0)

Возможным источником проблемы, из-за которой возникает событие LHS, и, следовательно, простой процессора, является операция в Листинг 20, которая сохраняет атрибуты значения с плавающей точкой во внутреннюю переменную value, а затем загружающая их в переменную i. В процессоре POWER7 отсутствует встроенная инструкция для побитового перемещения значения из регистра с плавающей точкой в регистр с фиксированной точкой. Подобное действие на архитектуре POWER выполняется путём сохранения числа с плавающей точкой из регистра с плавающей точкой в область памяти с помощью операции сохранения (store), а затем загрузка этой области памяти в универсальный регистр с фиксированной точкой. Так как доступ к памяти выполняется медленнее, чем операции с регистрами (даже при обращении к данным в кэше L1), то CPU "простаивает" в ожидании пока завершится загрузка, следующая за сохранением.

Примечание: Подробную информацию по этому вопросу можно найти в документе "POWER ISA 2.06 (POWER7)" (см. раздел "Ресурсы").

Довольно часто события, порождённые счётчиками производительности, вызывают прерывания, которые сохраняют PC-адрес инструкции, которая находится рядом с исполняющимися инструкциями. Это может привести к недостаточно точному аннотированию сборки. Чтобы устранить этот недостаток в архитектуре POWER начиная с четвертого поколения появился фиксированный набор счётчиков производительности, которые были названы "помеченными" (англ. marked). Такие инструкции генерируют меньше событий за тот же промежуток времени, поэтому индикация PC-инструкции будет точной, что приведет к более точному аннотированию сборки. В списке счётчиков Oprofile, который можно получить командой opcontrol -l, "помеченные" события можно идентифицировать по префиксу PM_MRK.

Чтобы ещё раз проверить результаты анализа, пронаблюдаем за счётчиком PM_MRK_LSU_REJECT_LHS. Оба счётчика PM_MRK_LSU_REJECT_LHS and PM_LSU_REJECT_LHS отслеживают одно и тоже событие. Однако "помеченный" счётчик (PM_MRK_LSU_REJECT_LHS) будет генерировать меньше событий за тот же период времени, но в результате сборка будет аннотирована более точно (см. Листинг 21).

Листинг 21. Запуск команды perf для отслеживания события PM_MRK_LSU_REJECT_LHS для архитектуры POWER7
 $ perf record -C 0 -e rd082 taskset -c 0 ./hypot_bench_glibc $ perf report Events: 256K raw 0xd082 64.61% hypot_bench_gli libm-2.12.so [.] __ieee754_hypot 35.33% hypot_bench_gli libm-2.12.so [.] __GI___finite

В результате профилирования будет получен результат, приведенный в Листинг 22.

Листинг 22. Отчёт команды perf по событию PM_MRK_LSU_REJECT_LHS для архитектуры POWER7
 : 00000080fc38b730 <.__ieee754_hypot>: [...] 1.23 : 80fc38b7a8: c9 a1 00 70 lfd f13,112(r1) 0.00 : 80fc38b7ac: f8 01 00 70 std r0,112(r1) 32.66 : 80fc38b7b0: c8 01 00 70 lfd f0,112(r1) [...] 0.00 : 80fc38b954: f8 01 00 70 std r0,112(r1) 0.00 : 80fc38b958: e8 0b 00 00 ld r0,0(r11) 0.00 : 80fc38b95c: 79 00 00 0e rldimi r0,r8,32,0 61.72 : 80fc38b960: c9 61 00 70 lfd f11,112(r1 [...]

Как видно, более 35% тех же самых событий приходится уже на другой символ, указанный в Листинг 23.

Листинг 23. Подробная информация из отчёта инструмента perf
 : 00000080fc3a2610 <.__finitel>> 0.00 : 80fc3a2610: d8 21 ff f0 stfd f1,-16(r1) 100.00 : 80fc3a2614: e8 01 ff f0 ld r0,-16(r1)

Основываясь на полученной информации, оптимизацию следует сконцентрировать на устранении задержек, вызванных преобразованием значений с плавающей точкой в целочисленные значения. POWER-процессор обладает быстрым и эффективным модулем для исполнения инструкций с плавающей точкой, поэтому нет необходимости выполнять подобные вычисления в целочисленном режиме. Из текущей реализации алгоритма данной функции, который используется в библиотеке GLIBC (файл sysdeps/powerpc/fpu/e_hypot.c) для платформы POWER, все задержки, связанные с событием LHS, были устранены благодаря полному переходу на FP-операции. В результате был получен более простой алгоритм, приведенный в Листинг 24.

Листинг 24. Исходный код функции hypot() из библиотеки GLIBC для платформы PowerPC
 double __ieee754_hypot (double x, double y) { x = fabs (x); y = fabs (y); TEST_INF_NAN (x, y); if (y > x) { double t = x; x = y; y = t; } if (y == 0.0 || (x / y) > two60) { return x + y; } if (x > two500) { x *= twoM600; y *= twoM600; return __ieee754_sqrt (x * x + y * y) / twoM600; } if (y < twoM500) { if (y <= pdnum) { x *= two1022; y *= two1022; return __ieee754_sqrt (x * x + y * y) / two1022; } else { x *= two600; y *= two600; return __ieee754_sqrt (x * x + y * y) / two600; } } return __ieee754_sqrt (x * x + y * y); }

Макрос TEST_INF_NAN - это ещё одна небольшая оптимизация, проверяющая, что число не является NaN или INFINITY перед выполнением последующих FP-операций. Это необходимо, так как действия с NaN или INFINITY значениями могут привести к возникновению FP-исключений, и поэтому спецификация функции не допускает подобных значений. На платформе POWER7 функции isinf и isnan были оптимизированы компилятором для FP-инструкций так, чтобы не выполнять лишних вызовов других функций, в то время как на процессорах POWER6 и более ранних эти действия генерируют вызовы к соответствующим функциям. В оптимизированной версии используется примерно такая же реализация, но она построена на inline-функциях, а не на вызовах полноценных функций.

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

Листинг 25. Бенчмарк-программа для стандартной реализации функции hypot() из пакета GLIBC
 $ /usr/bin/time ./hypot_bench_glibc INF_CASE : elapsed time: 14:994339 NAN_CASE : elapsed time: 14:707085 TWO60_CASE : elapsed time: 12:983906 TWO500_CASE : elapsed time: 10:589746 TWOM500_CASE : elapsed time: 11:215079 NORMAL_CASE : elapsed time: 15:325237 79.80user 0.01system 1:19.81elapsed 99%CPU (0avgtext+0avgdata 151552maxresident)k 0inputs+0outputs (0major+48minor)pagefaults 0swaps

В Листинг 26 приведены результаты для оптимизированной версии.

Листинг 26. Бенчмарк-программа для усовершенствованной реализации функции hypot() из пакета GLIBC
 $ /usr/bin/time ./hypot_bench_glibc INF_CASE : elapsed time: 4:667043 NAN_CASE : elapsed time: 5:100940 TWO60_CASE : elapsed time: 6:245313 TWO500_CASE : elapsed time: 4:838627 TWOM500_CASE : elapsed time: 8:946053 NORMAL_CASE : elapsed time: 6:245218 36.03user 0.00system 0:36.04elapsed 99%CPU (0avgtext+0avgdata 163840maxresident)k 0inputs+0outputs (0major+50minor)pagefaults 0swaps

Окончательное улучшение производительности составило более 100%, а время исполнения бенчмарк-программы для оптимизированной версии сократилось вдвое.

Заключение

Оценка производительности путём профилирования аппаратных счётчиков – это мощный инструмент для понимания того, как рабочая нагрузка ведёт себя на конкретном процессоре и в каком месте следует приложить усилия для оптимизации производительности. В новейшем процессоре POWER7 для использования доступны сотни счётчиков производительности, так что мы представили только самую простую модель для поиска зависимостирабочей нагрузки от простоев CPU. Понимание CBM-модели POWER7 иногда может оказаться затруднительным, так что мы также рассказали о Linux-инструментах, способных упростить этот процесс. Стратегии оценки производительности фокусируются на том, как искать "горячие точки" в программе, как понять шаблон доступа к памяти, используемый в приложении и как использовать CBM-модель POWER7. В конце мы использовали недавнюю оптимизацию, выполненную для тригонометрической функции hypot() в пакете GLIBC, чтобы пояснить анализ производительности, который и привёл к появлению оптимизированной версии кода.

Приложение

Настоящим предоставляется разрешение копировать, распространять и/или изменять этот документ в рамках лицензии GNU Free Documentation License, Version 1.3. Копия лицензии доступна по данному URL-адресу.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=855258
ArticleTitle=Измерение производительности Linux на компьютерах POWER
publish-date=01172013