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

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

Статья рассказывает, как на платформе POWER® можно измерить потери производительности ОС Linux®, связанные с компилируемыми языками, например, С или С++. В этой статье разбирается CPI-модель POWER7® и объясняется, как можно использовать стандартные инструменты Linux для поиска возможных точек останова процессора, сбоев конвейерной обработки и проблем, связанных с производительностью. В заключение дан пример анализа и оптимизации алгоритма для POWER7.

Адхимервал Занелла Нетто, программист, IBM

Адхимервал Занелла НеттоАдхимервал Нетто (Adhemerval Netto) работает разработчиком ПО в центре Linux-технологий компании IBM, занимаясь анализом производительности и разработкой пакета инструментов toolchain для архитектуры POWER. Он отвечает за различные отчёты по производительности ОС Linux на платформе POWER на Web-сайте spec.org и участвует в процессе разработки open-source библиотеки glibc.



Райан С. Арнольд, программист-консультант, IBM

Райан С. АрнольдРайан Арнольд (Ryan Arnold) работает программистом-консультантом в центре Linux-технологий компании IBM. Он обладает опытом интеграции пакета GNU toolchain и разработки системных библиотек для Linux. Райан также осуществляет поддержку библиотеки GLIBC на платформе POWER и поддержку библиотеки Libdfp. Он также является соавтором книги "Power Architecture 32-bit Application Binary Interface Supplement 1.0."



17.01.2013

Введение

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

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

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

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

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

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

В архитектуре 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-адресу.


Загрузка

ОписаниеИмяРазмер
Бенчмарк-программа для функции hypothypot_bench.tar.gz6KB
Сценарий для форматирования результатов perfpower7_cbm.zip2KB

Ресурсы

Научиться

Получить продукты и технологии

  • Web-сайт проекта OProfile содержит много дополнительной информации об этом инструменте.
  • Узнайте больше об инструменте perf, исходный код которого поддерживается в рамках проекта ядра Linux.
  • На Web-сайте проекта libpfm4 можно найти оригинальные коды событий POWER7, используемые утилитой perf.
  • Посетите Web-страницу библиотеки GNU C.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


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