Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 16.05.20 05:28
Оценка: 5 (1)
Профилирование кода обычно делается, как известно, методом Монте-Карло, который позволяет оценить распределение нагрузки с точностью до периода таймерного прерывания. Периодичность порядка 1 мс дает хорошие результаты для типового кода, но для программ, обрабатывающих тысячи событий в секунду, требуется сильное уменьшение периода, а это и дает большие накладные расходы, и портит результат. Кроме того, такой метод часто неприменим для профилирования низкоуровневого кода — тех же обработчиков прерываний, процедур планировщика ОС и т.п.

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

В MS VC++ уже очень давно поддерживается автоматическая вставка вызова служебной функции _penter при входе в каждую функцию (/Gh) и _pexit — при выходе (/GH). По идее, фича чертовски мощная, но, как и многое другое у MS, реализована столь же чертовски убого — функцию, вызвавшую _penter/_pexit, можно определить только по адресу возврата, для этого нужно или каждый раз лезть в общую базу данных, или тупо складывать все вызовы в общий лог. Доступ к базе — сильные тормоза, синхронизация доступа к логу — тоже тормоза.

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

В каких-нибудь других реализациях C/C++ компиляторы поддерживают разумные средства профилирования/отладки?
профилирование profiling отладка debug _penter _pexit
Re: Поддержка компиляторами профилирования и отладки кода
От: Alexander G Украина  
Дата: 25.05.20 04:33
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

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


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

Это не так уж и сложно.
Можно объявить массив со счётчиками в каждой единице трансляции (через static), индекс которого брать __LINE__ в месте обращения. Т.е. уникальность слотов для профилирования по __LINE__, лишние слоты -- ладно с ними.
В конце единицы трансляции -- фактическое определение этого массива, уже с максимальным __LINE__.

В местах обращения конструировать RAII класс на скоуп. Благодаря __LINE__, __FUNCTION__, макро везде будет одинаковым.
Для уменьшения оверхеда -- никаких функций, только rdtsc, и никакой синхронизации, только TLS.
Русский военный корабль идёт ко дну!
Re[2]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.05.20 05:01
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>На каждую функцию таки плохо. Наличие вставки сильно искажает картину.


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

AG>С другой стороны, местами нужно мерять куски финкции.

AG>Поэтому ручное инструментирование — самое оно.

Этого у меня есть. Просто досадно регулярно видеть такие вот решения, которым лишь самую малость недостает до тех самых 20%, чтобы давали 80% удобства и эффективности.

AG>никакой синхронизации, только TLS.


В user-mode оно значительно проще. А в kernel-mode проблема в первую очередь с TLS. О том и речь, что адекватная реализация _penter/_pexit могла бы самой MS помочь в профилировании ядра, чтоб не приходилось потом десятилетиями находить и исправлять редкие, но совершенно чудовищные (до десятков милли секунд) задержки.
Re: Поддержка компиляторами профилирования и отладки кода
От: кт  
Дата: 07.06.20 10:22
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>В каких-нибудь других реализациях C/C++ компиляторы поддерживают разумные средства профилирования/отладки?

Тут бы очень пригодилась аппаратная поддержка профилирования, но пока это все мечты...
Re: Поддержка компиляторами профилирования и отладки кода
От: kov_serg Россия  
Дата: 07.06.20 10:43
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Профилирование кода обычно делается, как известно, методом Монте-Карло, который позволяет оценить распределение нагрузки с точностью до периода таймерного прерывания. Периодичность порядка 1 мс дает хорошие результаты для типового кода, но для программ, обрабатывающих тысячи событий в секунду, требуется сильное уменьшение периода, а это и дает большие накладные расходы, и портит результат. Кроме того, такой метод часто неприменим для профилирования низкоуровневого кода — тех же обработчиков прерываний, процедур планировщика ОС и т.п.


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


ЕМ>В MS VC++ уже очень давно поддерживается автоматическая вставка вызова служебной функции _penter при входе в каждую функцию (/Gh) и _pexit — при выходе (/GH). По идее, фича чертовски мощная, но, как и многое другое у MS, реализована столь же чертовски убого — функцию, вызвавшую _penter/_pexit, можно определить только по адресу возврата, для этого нужно или каждый раз лезть в общую базу данных, или тупо складывать все вызовы в общий лог. Доступ к базе — сильные тормоза, синхронизация доступа к логу — тоже тормоза.

...
ЕМ>В каких-нибудь других реализациях C/C++ компиляторы поддерживают разумные средства профилирования/отладки?

  В MS VC++ есть PGO
// pgoautosweep.cpp
// Compile by using: cl /c /GL /W4 /EHsc /O2 pgoautosweep.cpp
// Link to instrument: link /LTCG /genprofile pgobootrun.lib pgoautosweep.obj
// Run to generate data: pgoautosweep
// Merge data by using command line pgomgr tool:
// pgomgr /merge pgoautosweep-func1!1.pgc pgoautosweep-func2!1.pgc pgoautosweep.pgd
// Link to optimize: link /LTCG /useprofile pgobootrun.lib pgoautosweep.obj

#include <iostream>
#include <windows.h>
#include <pgobootrun.h>

void func2(int count)
{
    std::cout << "hello from func2 " << count << std::endl;
    Sleep(2000);
}

void func1(int count)
{
    std::cout << "hello from func1 " << count << std::endl;
    Sleep(2000);
}

int main()
{
    int count = 10;
    while (count--)
    {
        if (count < 3)
            func2(count);
        else
        {
            func1(count);
            if (count == 3)
            {
                PgoAutoSweep("func1");
            }
        }
    }
    PgoAutoSweep("func2");
}

cl /c /GL /W4 /EHsc /O2 test-pgo.cpp
link /LTCG /genprofile pgobootrun.lib test-pgo.obj
test-pgo.exe
link /LTCG /useprofile pgobootrun.lib test-pgo.obj
А чем не устраивают штатные инструменты. Еще интела и нвиды.
Отредактировано 07.06.2020 10:44 kov_serg . Предыдущая версия .
Re: Поддержка компиляторами профилирования и отладки кода
От: prog123 Европа  
Дата: 07.06.20 14:49
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>В каких-нибудь других реализациях C/C++ компиляторы поддерживают разумные средства профилирования/отладки?


gprof выдает flat profile и сall graph
Re[2]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 07.06.20 15:22
Оценка:
Здравствуйте, кт, Вы писали:

кт>Тут бы очень пригодилась аппаратная поддержка профилирования


Не думаю, что реализация такого в каждом процессоре экономически оправдана. Для профилирования любого кода вполне достаточно TSC или другого таймера с быстрым опросом.
Re[2]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 07.06.20 15:43
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>А чем не устраивают штатные инструменты. Еще интела и нвиды.


Тем, что все эти инструменты работают по методу Монте-Карло. Они годятся для профилирования типовых программ, чтобы понять, где какую-нибудь сортировку пузырьком пора менять на более быструю. Для кода, обрабатывающего тысячи и десятки тысяч событий в секунду, они не годятся в принципе.
Re[2]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 07.06.20 15:45
Оценка:
Здравствуйте, prog123, Вы писали:

P>gprof выдает flat profile и сall graph


gprof — это отдельный профилировщик. А что есть в самом гнусном компиляторе для поддержки профилирования?
Re[3]: Поддержка компиляторами профилирования и отладки кода
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 07.06.20 16:45
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:


P>>gprof выдает flat profile и сall graph


ЕМ>gprof — это отдельный профилировщик. А что есть в самом гнусном компиляторе для поддержки профилирования?


Ты доку таки не пробовал читать? Сначала код компилируется с -pg (что даёт учитывающие прологи и эпилоги), а затем его выхлоп анализирует gprof.
The God is real, unless declared integer.
Re[4]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 07.06.20 17:55
Оценка:
Здравствуйте, netch80, Вы писали:

N>Ты доку таки не пробовал читать?


Которую? В списке софта на gnu.org gprof отсутствует. Гугль выдает на первом месте эту, там никаких подробностей нет.

N>Сначала код компилируется с -pg (что даёт учитывающие прологи и эпилоги)


Вот меня и интересует, какие именно прологи/эпилоги там генерятся. Это где-нибудь толком описано? Гугль по запросам типа "gсс profile function call prolog epilog" ничего вразумительного не находит.
Re[5]: Поддержка компиляторами профилирования и отладки кода
От: prog123 Европа  
Дата: 07.06.20 20:02
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, netch80, Вы писали:


N>>Ты доку таки не пробовал читать?


ЕМ>Которую? В списке софта на gnu.org gprof отсутствует. Гугль выдает на первом месте эту, там никаких подробностей нет.


N>>Сначала код компилируется с -pg (что даёт учитывающие прологи и эпилоги)


ЕМ>Вот меня и интересует, какие именно прологи/эпилоги там генерятся. Это где-нибудь толком описано? Гугль по запросам типа "gсс profile function call prolog epilog" ничего вразумительного не находит.


Не знаю про прологи и эпилоги, но например ф-ция
int sum(int a, int b)
{
    return a + b;
}


с опцией -pg превращается в
sum(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 8
1:      call        mcount
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        leave
        ret


т.е. вставляется только 'call mcount' которая описана по твоей ссылке.
Re[6]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 07.06.20 20:19
Оценка:
Здравствуйте, prog123, Вы писали:

P>т.е. вставляется только 'call mcount' которая описана по твоей ссылке.


Тогда это еще более убогая "поддержка", нежели в VC++. Сама по себе позволяет только подсчет количества вызовов, а подсчет времени работы — только с помощью грязных хаков вроде подмены адреса возврата в стеке. И нет никакой привязки к функции — опять нужно или искать по таблице/карте, или сваливать все результаты в общий лог, синхронизируя между потоками. То есть, для профилирования ядерного кода вообще и обработчиков прерываний в частности, не годится категорически.
Re[7]: Поддержка компиляторами профилирования и отладки кода
От: prog123 Европа  
Дата: 07.06.20 20:30
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, prog123, Вы писали:


P>>т.е. вставляется только 'call mcount' которая описана по твоей ссылке.


ЕМ>Тогда это еще более убогая "поддержка", нежели в VC++. Сама по себе позволяет только подсчет количества вызовов, а подсчет времени работы — только с помощью грязных хаков вроде подмены адреса возврата в стеке. И нет никакой привязки к функции — опять нужно или искать по таблице/карте, или сваливать все результаты в общий лог, синхронизируя между потоками. То есть, для профилирования ядерного кода вообще и обработчиков прерываний в частности, не годится категорически.


А почему с++ компилятор должен профилировать ядро и прерывания? Для этого нужно что-то типа perf в Линуксе, и то я не уверен.
Re[7]: Поддержка компиляторами профилирования и отладки кода
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 07.06.20 20:31
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

P>>т.е. вставляется только 'call mcount' которая описана по твоей ссылке.


ЕМ>Тогда это еще более убогая "поддержка", нежели в VC++. Сама по себе позволяет только подсчет количества вызовов, а подсчет времени работы — только с помощью грязных хаков вроде подмены адреса возврата в стеке. И нет никакой привязки к функции — опять нужно или искать по таблице/карте, или сваливать все результаты в общий лог, синхронизируя между потоками.


Я не знаю, что для тебя "нет привязки к функции", но выход gprof позволяет получить не только общее время и количество вызовов для каждой функции, но и какая часть этого времени потрачена под какой вызывающей функцией.
Может, это и делается через подмену адреса, для userland это вполне работает.

EM> То есть, для профилирования ядерного кода вообще и обработчиков прерываний в частности, не годится категорически.


Обработчики прерываний — да, наверно, так.
А как ты вообще собрался их профилировать без виртуалки или спец. железа?
The God is real, unless declared integer.
Re[8]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 08.06.20 06:09
Оценка:
Здравствуйте, prog123, Вы писали:

P>А почему с++ компилятор должен профилировать ядро и прерывания?


Он не должен, но может.

P>Для этого нужно что-то типа perf в Линуксе, и то я не уверен.


Если прочитаете тред с начала, то поймете.
Re[8]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 08.06.20 06:13
Оценка:
Здравствуйте, netch80, Вы писали:

N>Я не знаю, что для тебя "нет привязки к функции"


Прочитать тред с начала — категорически не?

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


Охотно верю, что может. Вопрос — какой ценой.

N>Может, это и делается через подмену адреса, для userland это вполне работает.


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

N>А как ты вообще собрался их профилировать без виртуалки или спец. железа?


В первом сообщении я это описал.
Re[9]: Поддержка компиляторами профилирования и отладки кода
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 08.06.20 07:03
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

N>>Я не знаю, что для тебя "нет привязки к функции"

ЕМ>Прочитать тред с начала — категорически не?

Читал. Но данная формулировка всё равно непонятна. Если ты имеешь в виду, что каждая функция получит свой персональный идентификатор, то
1) Как будет выглядеть этот идентификатор? Например, если это следующее целое из последовательности, собранной линкером, то как ты будешь разделять такие значения для разных DLL?
2) Чем тебе не годится адрес самой функции в памяти, если он уникален в пределах запущенного процесса (или ядра) и он элементарно достаётся из стека?

Можешь сформулировать свою концепцию на уровне ассемблера и структур данных — как будет выглядеть код в профилируемой функции и как предложенные _penter/_pexit будут извлекать нужные данные?

И ещё одно — время, затраченное в функции, может сильно зависеть от пути, которым к ней пришли. Профилирование в GCC сохраняет этот путь, что очень помогает. Какая твоя альтернатива, кроме записи всего стека (ну или, очевидно, переписки в разные функции-прокси)?

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

ЕМ>Охотно верю, что может. Вопрос — какой ценой.
N>>Может, это и делается через подмену адреса, для userland это вполне работает.
ЕМ>Это работает везде, но вносит накладные расходы, радикально искажающие картину на микро- и наносекундных интервалах. Вроде того, что в световой микроскоп невозможно четко рассмотреть структуру молекулы.

А ты думаешь, твой подход позволит не вносить подобные искажения? Я пока не вижу, из чего такое может получиться. Один только вызов этих _penter/_pexit уже внесёт радикальные искажения.

N>>А как ты вообще собрался их профилировать без виртуалки или спец. железа?

ЕМ>В первом сообщении я это описал.

Да, я его читал с самого начала. Проблема одна: нет причины верить, что это что-то так радикально даст. Таки попробуй рассказать какой-то реальный вариант, как это улучшит.
The God is real, unless declared integer.
Re[10]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 08.06.20 09:33
Оценка:
Здравствуйте, netch80, Вы писали:

N>Если ты имеешь в виду, что каждая функция получит свой персональный идентификатор


Прежде всего, это должен быть не "идентификатор", а "дескриптор". Который для удобства может содержать и идентификатор (например, сигнатуру), но главное, что там должно быть — это рабочие поля для профилирующего кода.

N>2) Чем тебе не годится адрес самой функции в памяти, если он уникален в пределах запущенного процесса (или ядра) и он элементарно достаётся из стека?


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

N>Можешь сформулировать свою концепцию на уровне ассемблера и структур данных — как будет выглядеть код в профилируемой функции и как предложенные _penter/_pexit будут извлекать нужные данные?


Я ж вроде сформулировал в исходном сообщении. Компилятор должен создать для каждой функции дескриптор с некоторым (в идеале — задаваемым через ключ командной строки) размером рабочей области профилировщика. Адрес дескриптора должен передаваться в _penter/_pexit (например, через EAX/RAX для первой, через ECX/RCX для второй). Для удобства дескрипторы нужно создавать в отдельной секции, чтобы можно было обложить ее двумя секциями снизу/сверху, и перебрать все дескрипторы в таблице.

N>И ещё одно — время, затраченное в функции, может сильно зависеть от пути, которым к ней пришли. Профилирование в GCC сохраняет этот путь, что очень помогает. Какая твоя альтернатива, кроме записи всего стека (ну или, очевидно, переписки в разные функции-прокси)?


mcount в GCC дает еще меньше возможностей, чем даже текущая реализация _penter/_pexit в VC++. Путь всяко придется добывать отдельно — или раскруткой стека, или записью лога. Если это нужно, профилировщик сможет это делать при любом раскладе. Моя идея с дескрипторами никак не ограничивает текущую реализацию, а лишь расширяет ее, с сохранением полной обратной совместимости.

N>А ты думаешь, твой подход позволит не вносить подобные искажения? Я пока не вижу, из чего такое может получиться. Один только вызов этих _penter/_pexit уже внесёт радикальные искажения.


Какие "радикальные"? Сам по себе вызов функции довольно дешев, он может исказить поведение разве что микрофункции, выполняющей несколько простых команд. Если такая микрофункция достаточно часто вызывается через call — она и без профилирования создает приличные накладные расходы.

N>Таки попробуй рассказать какой-то реальный вариант, как это улучшит.


Ну вот у меня, например, по таймеру, генерящему события через 500 мкс — 1 мс, несколько параллельных кодовых потоков (threads) обрабатывают несколько звуковых потоков. Если профилировщик каждые 500 мкс станет искать свои рабочие данные для каждой из вызываемых функций по ее адресу, он внесет изрядные искажения во время работы даже не самых мелких функций. А если он будет тупо сваливать в лог адрес функции и время входа/выхода, то лог будет очень быстро расти, и его придется синхронизировать между потоками. В пределе, конечно, можно выделить непрерывный участок в памяти, и продвигать указатель посредством lock xadd, но nonpaged pool все равно не резиновый.
Re[11]: Поддержка компиляторами профилирования и отладки кода
От: IID Россия  
Дата: 30.06.20 12:08
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>В пределе, конечно, можно выделить непрерывный участок в памяти, и продвигать указатель посредством lock xadd, но nonpaged pool все равно не резиновый.


Кольцевой буфер в NPaged, и его своп из Passive потока в Paged.
kalsarikännit
Re[12]: Поддержка компиляторами профилирования и отладки кода
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 30.06.20 13:48
Оценка:
Здравствуйте, IID, Вы писали:

IID>Кольцевой буфер в NPaged, и его своп из Passive потока в Paged.


Да понятно, что извернуться можно по-всякому. Только с вывертами нужно еще и лог разбирать при помощи PDB, а при минимальной поддержке компилятора можно было бы сразу выводить приемлемую статистику.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.