Программирование DSP VisualDSP++: подсчет циклов и профайлинг Sat, October 21 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


VisualDSP++: подсчет циклов и профайлинг Печать
Добавил(а) microsin   

Среда разработки VisualDSP++® предоставляет утилиты, которые можно использовать для профайлинга приложения в целях повышения его эффективности (скорости работы). Эти утилиты дают возможность анализировать Ваше приложение, определять и устранять в нем узкие, высоконагруженные места и оптимизировать код. Путем использования профилей выполнения кода (execution profiles), разработчики приложения могут тонко настроить распределение памяти, чтобы достичь наилучшей производительности системы. Предоставленный инструментарий включает:

• Линейный профайлер (Linear Profiler), работающий только совместно с симулятором.
• Статистический профайлер (Statistical Profiler), утилита для использования эмулятора и отлаживаемых целевых процессоров на платах разработчика EZ-KIT Lite®.
• Встроенный профайлинг (Embedded profiling), реализуемый с помощью встроенного в процессор аппаратного счетчика тактов (hardvare cycle count).

Инструменты Linear Profiling и Statistical Profiling, которые не нарушают нормальный ход выполнения программы, дают профиль, отображающий время выполнения по тактам инструкций кода, т. е. сколько тактов тратится процессором на выполнение определенных мест в коде, и количество чтений и записей памяти, которые осуществлялись в приложении.

Linear Profiler делает выборки по каждой выполняемой инструкции (по изменениям значения программного счетчика PC), что тает полную и точную картину эффективности кода. Однако платить за эту точность приходится тем, что это медленный метод профайлинга приложения, работающий только в симуляторе процессора, так что этот метод неприменим для анализа на реальном аппаратном устройстве.

Statistical Profiler работает быстрее, позволяя Вам делать профайлинг без какого-либо влияния на характеристики реального времени выполнения кода в приложении. Statistical Profiler достигает этого путем периодического опроса PC. Это не настолько точно, как у Linear Profiler, однако дает возможность анализа кода на аппаратном устройстве.

Библиотеки VisualDSP++ также предоставляют возможность получить точные значения счетчика тактов, и тем самым измерять время выполнения отдельных участков кода приложения (осуществляется вычисление и возврат информации аппаратного счетчика тактов). Это реализовано на основе регистров счетчика тактов и макросов, предоставленных в этих библиотеках.

Этот документ (перевод даташита EE-332 [1]) описывает функциональное поведение инструментов профайлинга, их использование, возможности и ограничения. Дополнительно EE-332 сопровождается примерами кода для процессоров Blackfin, SHARC и TigerSHARC, чтобы показать различия между результатами профайлинга, которые возвращаются от симулятора и от аппаратуры. Следующие указания применимы ко всем архитектурам: Blackfin®, SHARC® и TigerSHARC®.

[Статистический и линейный профайлеры]

Linear Profiler, который доступен только для сессий симулятора, включается через меню Tools -> Linear Profiling.

Statistical Profiler, который доступен только для сессий эмулятора или плат разработчика EZ-KIT Lite, включается через меню Tools -> Statistical Profiling.

Можно конфигурировать некоторое количество опций профайлинга для тонкой настройки требуемой для Вас профилируемой информации. Эти опции доступны через пункт Properties контекстного меню окна профайлера (открывается правым кликом).

Закладка Display (см. рис. 1) позволяет Вам указать тип памяти и связанные метрики, которые хотите профилировать. Доступные типы памяти специфичны для определенной цели отладки (процессор + отладчик), и доступные метрики специфичны для определенного типа памяти. Опциональные метрики позволяют Вам подключить такие данные, как количество чтений/записей по памяти, количество попаданий/промахов кеша, счетчики тактов выполнения кода, и так далее, что будет доступно в профилируемых данных.

VisualDSP Profile Window Properties Display tab

Рис. 1. Внешний вид закладки Display диалога настройки свойств сессии отладки.

Примечание: дешевые эмуляторы JTAG наподобие ICE-100B дают довольно бедное количество данных профайлинга - только процентное использование тактов процессора для участков кода с их адресами и символьными метками + гистограмму загрузки, но уже и это очень неплохо. Также следует иметь в виду, что опциональные метрики имеются только для сессий симулятора процессоров Blackfin и TigerSHARC. Они недоступны на аппаратных сессиях отладчика или сессиях симулятора процессоров SHARC.

Закладка Filter (рис. 2) позволит Вам указать, что нужно профилировать. Вы можете выбрать профайлинг всего пространства памяти, выбранные функции C/C++, или указанные области памяти. Указание этих опций позволит генерировать более краткий профиль, содержащий только требуемую информацию.

VisualDSP Profile Window Properties Filter tab

Рис. 2. Внешний вид закладки Filter диалога настройки свойств сессии отладки.

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

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

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

VisualDSP Left pane Statistical Profiler

Рис. 3. Левая часть окна Statistical Profiler, где отображена информация о загруженности кода функциями в процентном отношении.

VisualDSP Right pane Statistical Profiler

Рис. 4. Правая часть окна Statistical, показывающая информацию загруженности процессора в контексте исходного кода приложения.

Результаты профайлинга можно сохранить в файл .txt, .xml или .prf, что позволит (только для форматов .prf и .xml) загрузить эти результаты позже для повторного анализа, или для объединения (только для форматов .prf и .xml only) с открытым профилем, чтобы представить соединенные данные, что даст более ясную картину о производительности работы Вашего приложения в разных вариантах запуска. Для соединения данных сохраненный профиль должен иметь один и тот же тип целевого процессора и памяти, как у открытого профиля, и должен использовать те же опциональные метрики профиля. Все эти опции доступны через меню Tools -> Statistical Profiling или Tools -> Linear Profiling.

[Библиотечные функции для подсчета тактов]

Внимание: макросы подсчета тактов требуют использования опции -DDO_CYCLE_COUNTS командной строки компилятора.

Макросы подсчета тактов предоставляют 3 разных метода счета тактов:

• Базовый счет тактов (Basic cycle count) - вернет количество тактов, потраченных на выполнение секции (определенного участка) кода.
• Счет тактов со статистикой (Cycle count with statistics) - вернет среднее, минимальное и максимальное количество тактов, потраченное для выполнения участка кода.
• Счет времени с использованием time.h - вернет время выполнения участка кода, выраженное в секундах.

В релизах VisualDSP++ до версии 5.0 определены макросы с параметрами в заголовочных файлах cycle_count.h и cycles.h, которые разворачиваются компилятором на несколько операторов. Эта версия реализации привела к неожиданным побочным эффектам, так что возможно, что эти операторы не дадут точных результатов для предназначенных инструкций кода.

Рассмотрим следующий пример:

if (condition)
   STOP_CYCLE_COUNT(cnt2, cnt2);

Поскольку макрос STOP_CYCLE_COUNT разворачивается на несколько операторов, то только первый оператор этого развернутого макроса выполнится под управлением заданного условия. Остальные операторы будут выполнены всегда, так что подсчет тактов даст некорректные результаты.

Чтобы исправить эту ошибку, эти макросы теперь реализованы с использованием блока операторов (фигурные скобки). С предыдущими макросами, представляющими последовательность инструкций, было допустимо использовать макрос без завершающего символа точки с запятой; это больше не так для случая использования в VisualDSP++ 5.0.

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

Подсчет тактов на всех архитектурах использует два аппаратных 32-битных регистра. На процессорах Blackfin это регистры CYCLES и CYCLES2, на процессорах TigerSHARC это регистры CCNT0 и CCNT1, на процессорах SHARC это регистры EMUCLK и EMUCLK2. Первый из этой пары регистров инкрементируется каждый такт, и переваливает в 0 после достижения значения 0xffffffff (переход от -1 к 0). Когда первый из этих регистров переполнится и перейдет в 0, второй регистр инкрементируется на 1, так что в результате получается один 64-разрядный счетчик. Для процессоров Blackfin и TigerSHARC оба этих регистра используются в макросах подсчета тактов. Однако, чтобы экономить память и время выполнения, макросы для процессоров SHARC не используют регистр EMUCLK2. Если код анализируется для длинного промежутка времени, то регистр EMUCLK может переполниться, так что нужно будет также учитывать EMUCLK2, чтобы проводить точные измерения.

Базовый метод счета тактов. Это осуществляется тремя простыми макросами, которые определены в файле cycle_count.h: 

PRINT_CYCLES(T)
START_CYCLE_COUNT(S)
STOP_CYCLE_COUNT(T,S)

Параметр S в макросе START_CYCLE_COUNT инициализирует счет текущим значением (т. е. задает точку отсчета времени), и параметр T в макросе STOP_CYCLE_COUNT это общее количество тактов, полученное вычитанием S из текущего значения счетчика тактов, когда выполняется макрос STOP_CYCLE_COUNT(T,S). Также учитывается и удаляется незначительное количество тактов, затраченное на вызов макроса STOP_CYCLE_COUNT(), чтобы предоставить точный результат выполнения участка кода.

Пример использования дан в Листинге 1.

//Листинг 1. Базовый метод счета затраченного времени.
#include < cycle_count.h >
#include < stdio.h >
 
int main(void)
{
   cycle_t start_count;
   cycle_t final_count;
   
   START_CYCLE_COUNT(start_count);
   Some_Function_Or_Code_To_Measure();
   STOP_CYCLE_COUNT(final_count,start_count);
   PRINT_CYCLES("Number of cycles: ",final_count);
}

Счет тактов с накоплением статистики. Эта техника предоставляет 5 макросов: 

CYCLES_INIT(S)
CYCLES_START(S)
CYCLES_STOP(S)
CYCLES_PRINT(S)
CYCLES_RESET(S)

Параметр S инициализируется в 0 макросом CYCLES_INIT(S), и устанавливается в текущее значение регистра счетчика макросом CYCLES_START(S). Макрос CYCLES_STOP(S) распаковывает значение текущего счетчика регистров и накапливает статистику в S, основываясь на самом последнем вызове CYCLES_START(S). Макрос CYCLES_PRINT(S) печатает статистику для работающего кода, давая подробную информацию по среднему, минимальному и максимальному количеству тактов для измеряемого участка кода, и дает информацию о количестве проходов анализируемой секции кода.

Пример использования показан в Листинге 2.

//Листинг 2. Подсчет тактов с накоплением статистики.
#include < cycles.h >
#include < stdio.h >
 
int main(void)
{
   cycle_stats_t stats;
   int i;
   
   CYCLES_INIT(stats);
   for (i = 0; i < LIMIT; i++)
   {
      CYCLES_START(stats);
      Some_Function_Or_Code_To_Measure();
      CYCLES_STOP(stats);
   }
   CYCLES_PRINT(stats);
   CYCLES_RESET(stats);
}

Счет тактов с использованием time.h. Этот функционал использует тип данных времени тактов (clock_t) вместе с макросом CLOCKS_PER_SEC, чтобы вычислить время, которое было затрачено в функции или блоке кода. Используются две переменные типа clock_t: одна инициализируется значением регистра счетчика тактов в начале измеряемого блока кода, и вторая устанавливается в значение этого регистра счетчика, когда измеряемый блок заканчивается. Разница между этими двумя переменными делится на скорость тактирования, которая определяется макросом CLOCKS_PER_SEC, давая точное значение времени, которое потрачено на выполнение кода.

Пример использования показан в Листинге 3.

//Листинг 3. Отсчет времени работы кода с использованием time.h.
#include < time.h >
#include < stdio.h >
 
int main(void)
{
   volatile clock_t clock_start;
   volatile clock_t clock_stop;
   double secs;
 
   clock_start = clock();
   Some_Function_Or_Code_To_Measure();
   clock_stop = clock();
   secs = ((double) (clock_stop - clock_start)) / CLOCKS_PER_SEC;
   printf("Time taken is %e seconds\n",secs);
}

Подсчет тактов в коде на ассемблере. Чтобы измерить время выполнения кода в проекте на ассемблере, Вы можете просто напрямую обращаться к регистрам аппаратного счетчика тактов. Для процессора Blackfin см. пример в Листинге 4, для процессора TigerSHARC см. Листинг 5, для процессоров SHARC см. Листинг 6.

//Листинг 4. Подсчет тактов для Blackfin на ассемблере.
   R2 = 0;
   CYCLES = R2;
   CYCLES2 = R2;
   R2 = SYSCFG;
   BITSET(R2,1);
   SYSCFG = R2;
   /* Сюда вставьте анализируемый код */
   R2 = SYSCFG;
   BITCLR(R2,1);
   SYSCFG = R2;
   R2 = CYCLES
   R1 = CYCLES2

//Листинг 5. Подсчет тактов для TigerSHARC на ассемблере.
   xr2 = CCNT0;;
   /* Сюда вставьте анализируемый код */
   xr3 = CCNT0;;
   xr4 = r3 - r2;;

//Листинг 6. Подсчет тактов для SHARC на ассемблере.
   R6 = EMUCLK;
   /* Сюда вставьте анализируемый код */
   R7 = EMUCLK;
   R8 = R7 - R6;

[Что влияет на точность анализа]

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

STDIO, точки останова и пошаговое выполнение на аппаратуре. Когда Вы запускаете свое приложение на реальной аппаратуре, файловый ввод/вывод, STDIO, и пошаговое выполнение приводят процессор к остановке, каждая такая остановка приводит к сбросу (синхронизации) конвейера (flush pipeline). При пошаговом выполнении сбросы конвейера происходят на каждой пошагово выполняемой инструкции. Для операций файлового ввода/вывода и STDIO очистка конвейера происходит из-за того, что выполняются запуск/остановка (run/halt) используемая примитивами ввода/вывода при передаче информации. Типичным примером служит использование printf(), который выполняет в себе 3 операции: открытие (open), чтение (read) и затем закрытие (close). На каждом из этих событий процессор останавливается на скрытой точке останова, конвейер сбрасывается, выполняется запрошенная операция, и затем процессор снова запускается.

Макросы подсчета тактов могут давать результаты с сильной флуктуацией, в зависимости от того, как приложение выполняется по отношению к функциям I/O и пошаговому выполнению; всякий раз, когда происходит сброс конвейера, к регистрам счетчика тактов добавляются дополнительные такты. Это может дать весьма отличающиеся результаты затрат процессорного времени по сравнению с реальными условиями работы приложения, когда пошаговое выполнение и/или отладочный ввод/вывод не используется.

В контексте профайлинга остановки, которые могут происходить при использовании STDIO и пошаговом выполнении, приводят к отбрасыванию информации профайлинга. Если профилируемый код полагается на STDIO, или подвергается частым остановкам (либо при пошаговом выполнении, либо с задействованием точек останова), то профайлер не соберет достаточно данных выборок для анализа.

Если данные профайлинга были отброшены, то VisualDSP++ 5.0 IDDE сгенерирует следующее предупреждение: "Statistical profiling information has been discarded". Дополнительную информацию см. в разделе онлайн-подсказки "Statistical Profiling of Short Run Programs".

[Сравнение линейного и статистического профайлинга]

Сравнение симулятора и аппаратного отладчика (ICE/EZ-KIT Lite). Частый случай, когда результаты из профайлера отличаются для симулятора и аппаратной цели отладки. В зависимости от процессора, который используется, Вы можете увидеть разные результаты из профайлера или из макросов подсчета тактов в сравнении с другими целями отладки. Одна из возможных причин отличий между счетчиками тактов в симуляторе и счетчиками при использовании ICE / EZ-KIT Lite это способ, которым симулятор инициализирует модель чипа процессора.

На симуляторах TigerSHARC это осуществляется запуском части инициализации ядра загрузчика (loader kernel, см. сообщение в окне вывода "[Info: si1108] Running Default Loader: {имя_файла}"). Этот код разрешает кеш и branch target buffer (BTB). Когда код запускается на аппаратном TigerSHARC, это не происходит. Таким образом, чтобы получить сравнимые результаты, подключите инициализацию кеша и разрешите BTB в Вашем коде.

Симуляторы Blackfin конфигурируют некоторое количество архитектурных регистров в их значение после сброса. Подробнее см. руководство по используемому процессору (Processor Hardware Reference).

Симуляторы SHARC инициализируют многие свои регистры, основываясь на значениях, определенных в файлах .xml (находятся в папке System\Archdef каталога инсталляции VisualDSP++).

Аппаратные цели отладки (эмулятор+процессор) также инициализируют многие свои регистры на основе значений, определенных в файлах .xml (также находятся в папке System\Archdef каталога инсталляции VisualDSP++).

Поскольку значения в файле .xml может отличаться от используемых для инициализации симулятора Blackfin или TigerSHARC, аппаратура может прийти из сброса с другим состоянием, не совпадающим с состоянием симулятора. Симуляторы SHARC отличаются, поскольку установка их регистров такая же, как делают установку аппаратные цели отладки.

Если анализируемый код жестко полагается на активность шины и/или работу с внешней памятью, то счетчики тактов с симуляторе и на реальной аппаратуре могут отличаться. Хотя симулятор делает попытку моделировать латентность внешней памяти и задержки при доступе к внешней памяти (back to back load/store), все-таки они не будут точными в учете тактов. То же самое верно для взаимодействия с многими периферийными устройствами.

Сравнение линейного и статистического профайлинга. Иногда, когда Linear Profiler возвратил полный профиль, оказывается пропущенной функция в Statistical Profiler, или Statistical Profiler возвращает неполные данные профиля. Причина кроется в различных способах, каким захватывают данные профайлинга Linear Profiler и Statistical Profiler.

Поскольку Linear Profiler обрабатывает каждое изменение PC, то в полученных данных присутствуют все функции и результирующий профиль оказывается точным. С другой стороны, Statistical Profiler обрабатывает PC периодически, так что ему попадается меньше функций, которые могут быть пропущены в результирующем профиле. Дополнительно, если профилируемое приложение было прервано на коротком отрезке времени, то Statistical Profiler может не захватить достаточное количество данных, чтобы показать профиль выполнения кода, давая в результате чистый профиль.

Кроме того, если приложение было спрофилировано на большом количестве выборок с разными адресами PC, то значительно падает скорость выборок Statistical Profiler, что может привести к ненадежным результатам.

Эффекты, наблюдаемые на аппаратуре. Аппаратура тоже может давать эффекты наподобие пустого или неполного профиля. Результаты могут меняться в зависимости от того, как быстро разные эмуляторы и агенты отладки (EZ-KIT Lite Debug Agent) смогут собрать информацию профиля. Отладчики HPPCI-ICE и HPUSB-ICE быстро собирают выборки для профиля, в то время как USBICE и EZ-KIT Lite Debug Agent собирают выборки со значительно меньшей скоростью. Если было собрано слишком мало выборок профиля, то окно профайлинга может не показать какую-либо информацию, или результаты выполнения кода будут представлены неточно.

Основное правило использования Statistical Profiler: программа должна отработать достаточно долго, чтобы (a) собрать выборки, и (b) собрать достаточно выборок, чтобы профиль приложения стал стабильным.

Симуляторы Blackfin: Cycle-Accurate-симулятор в сравнении с Compiled-симулятором. Для процессоров Blackfin имеется 2 симулятора: симулятор с точной отработкой тактов (cycle-accurate) и скомпилированный симулятор (или так называемый быстрый, "fast functional" симулятор). Оба симулятора функционально корректны; однако, у каждого симулятора отдельная область применения. Только cycle-accurate симулятор может аппроксимировать задержки в приложении и получить реальное значение тактов и точные кривые производительности.

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

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

Кроме того, несмотря на то, что profiling API поддерживается в обоих симуляторах, оно не работает точно по циклам для быстрого функционального симулятора.

[Приложение A: пример профайлинга]

Назначение примера. К этому апноуту прилагается код примера [3] для использования вместе с EZ-KIT Lite Debug Agent и сессиями симулятора. Он предназначен для демонстрации различий в результатах профайлинга в симуляторе и на реальной аппаратуре.

Ожидаемые результаты. Функции a() и b() выполняют одинаковые действия, в то время как функция c() выполняет половину из этих операций, и d() выполняет половину от этой половины. В симуляторе профайлер должен вернуть результаты, близкие в соотношении a:b:c:d как 4:4:2:1. Вы должны также заметить, что такие функции как start и main(), перечислены в конечном профиле, несмотря на то, что они используют на выполнение очень мало тактов.

На платах разработчика EZ-KIT Lite через Debug Agent результаты профайлинга должны отличаться от результатов симулятора. Запуск и профайлинг проекта несколько раз должен показать разные процентные результаты функций для каждого запуска, и в некоторых случаях Вы должны видеть также, что функция d() полностью отсутствует в профиле, и возможно некоторые другие функции также отсутствуют. Причина в том, что что функция не собрала достаточно процессорного времени, и поэтому не попала в выборку из-за периодического характера выборок, который использует Statistical Profiler. Дополнительно обратите внимание, что такие функции, как start и main() не видны в профиле для сессии EZ-KIT Lite.

Если у Вас имеется высокопроизводительный аппаратный отладчик, такой как HPUSB-ICE или HPPCI-ICE, то запуск этого проекта постоянно будет давать профиль, содержащий все 4 функции; однако результаты все еще будут не такими полными, как результат Linear Profiler в симуляторе.

[Приложение B: пример подсчета тактов]

Назначение примера. Пример кода подсчета тактов [3], прилагаемый для этого документа, предназначен для использования с любыми подходящими сессиями процессоров Blackfin, SHARC и TigerSHARC, и здесь демонстрируется использование разных техник подсчета тактов, предоставленных библиотеками.

Ожидаемые результаты. Когда выполняются примеры подсчета тактов, то результаты будут выведены в STDIO для каждого из трех методов подсчета тактов. Ниже в листинге показан вывод подсчета тактов со статистикой для процессора ADSP-BF533.

Cycle Counting Facilities
------------------------
Basic Cycle Count:
Number of cycles:420036
------------------------
Cycle Count With Statistics:
      AVG : 420036
      MIN : 420036
      MAX : 420036
      CALLS : 10
------------------------
Cycle Count Using time.h:
Time taken was 7.071499e-04 seconds
------------------------

[Ссылки]

1. EE-332 Cycle Counting and Profiling site:analog.com.
2. Библиотека Blackfin DSP Run-Time, общее описание.
3. 170419EE-332.zip - документация и примеры кода.

 

Добавить комментарий


Защитный код
Обновить

Top of Page