Сопоставление начальных и конечных вызовов профилирования

В настоящее время я внедряю систему профилирования в приложение.

У меня есть две функции макроса, которые определены на основе флага компилятора (NDEBUG). Когда NDEBUG не определен, эти две функции (profilingStart и profilingEnd) генерируют отчеты о профилировании, которые показывают время вызова profilingStart и время вызова profilingEnd.

Проблема заключается в возможности возникновения несоответствия, т. е. сценария, когда profilingStart был вызван, а profilingEnd нет (или наоборот). Мой код уже распознает эти ситуации во время выполнения, но было бы предпочтительнее, если бы ошибка возникала во время компиляции из-за этого несоответствия.

Одно из предложений состояло в том, чтобы использовать do{...}while(); конструкция, обеспечивающая правильное сопряжение функций профилирования. Стартовая макрофункция будет содержать do{, а конечный макрос будет содержать }while(). Если один отсутствует, мы получим ошибку во время компиляции. Однако с этим есть некоторые проблемы — вы можете использовать вызовы profilingStart() и profilingEnd() только в начале и в конце профилируемой функции, поскольку их использование внутри функции может повлиять на область действия локальных переменных ( поскольку они могут выйти за рамки из-за вызова do{...}while()).

Еще одна идея, которая у меня была, — просто объявить переменную в функции profilingStart, а затем попытаться изменить содержимое этой переменной в функции profilingEnd. Это предотвращает проблемы с областью видимости и вызовет ошибку компилятора, если переменная никогда не была объявлена. Однако у меня никогда не было бы никакого метода проверки того, что содержимое переменной изменено в конечной функции. Это решает только половину проблемы, так как не проверяет вызов функции profilingEnd.

Любые комментарии приветствуются, как всегда. Заранее спасибо.

РЕДАКТИРОВАТЬ: Может возникнуть некоторая путаница в отношении моих комментариев относительно сферы действия. profilingStart() и profilingEnd() всегда будут вызываться внутри одной и той же функции. Они могут просто не вызываться в самом начале/самом конце функции. Вот пример того, что я имел в виду:

int DoSomething(void)
{
   profilingStart();
   int a;
   DoMath(a);
   profilingStop();
   return a; // a is out of scope here, as the do{...}while(0) construct has gone out of scope
}

person BSchlinker    schedule 06.07.2011    source источник
comment
Сопоставление начального и конечного вызовов: это работа соответственно конструктора и деструктора. Предположительно, вы подумали об этом и отклонили это как решение вашей проблемы. Почему?   -  person Cheers and hth. - Alf    schedule 06.07.2011
comment
@Alf: На самом деле это не решает проблему ОП. Вам по-прежнему нужно либо вызывать delete в нужном месте, либо искусственно вводить дополнительную вложенную область видимости для автоматического вызова деструктора в нужное время.   -  person Oliver Charlesworth    schedule 06.07.2011
comment
@jmein Это была моя цель.   -  person BSchlinker    schedule 06.07.2011
comment
Кроме того, ваш второй подход по-прежнему имеет проблему с областью действия, заключающуюся в том, что начальные и конечные функции должны выполняться в одном и том же блоке области (в противном случае конечная функция может не видеть начальную переменную). Другими словами, вы не можете вызвать start в одном методе и end в другом. Есть ли причина, по которой вы не хотите использовать gprof?   -  person Pace    schedule 06.07.2011
comment
@Pace: Gprof (AFAIK) имеет детализацию на уровне функций.   -  person Oliver Charlesworth    schedule 06.07.2011


Ответы (3)


В C++ одним из решений является использование идиомы "RAII". Что-то вроде этого:

class Profiler {
  public:
    Profiler() { profilingStart(); }
    ~Profiler() { profilingEnd(); }
}

Затем вы используете его следующим образом:

{ // start of block you want to profile
    Profiler prof;
    ...
}

Это гарантирует, что profilingEnd будет вызываться даже при наличии исключений, преждевременных возвратов, break и т. д. То есть это абсолютно гарантирует, что вызовы являются парными.

Однако это требует помещения кода, который вы хотите профилировать, в блок.

[редактировать]

Я пропустил, что вы хотите иметь возможность поместить profilingEnd в блок, отличный от profilingStart.

См. комментарий @Roddy ниже, чтобы узнать, как с этим справиться; например путем проверки деструктором, чтобы убедиться, что профилировщик был остановлен к моменту уничтожения объекта. Хотя это не обнаружит проблему во время компиляции, она обнаружит ее «рядом» с проблемой во время выполнения.

person Nemo    schedule 06.07.2011
comment
Это не сработает, если конец находится в другой функции/области действия по сравнению с началом (ОП упоминает, что это возможно). - person Oliver Charlesworth; 06.07.2011
comment
@Oli Charlesworth - может возникнуть путаница с моим первоначальным сообщением. Я уточнил. - person BSchlinker; 06.07.2011
comment
Вы можете избежать проблемы «конца в другой области», добавив метод stop() (и, возможно, функцию ручного запуска вместе с параметром автозапуска в конструктор: Profiler(bool autoStart = true) {...} ) к объекту, чтобы явно остановить его до истечения срока действия области. В деструкторе просто проверьте, не вызывалась ли уже команда stop. Тогда вы получаете гибкость И простоту использования RAII и безопасность исключений. - person Roddy; 06.07.2011

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

person Necrolis    schedule 06.07.2011
comment
Это не сработает, если конец находится в другой функции/области действия по сравнению с началом (ОП упоминает, что это возможно). - person Oliver Charlesworth; 06.07.2011
comment
@oli: он упоминает только область видимости, а не вызовы перекрестных функций, что в значительной степени неизбежно, если вы не готовы перенаправить объявление всех затронутых переменных в стиле старого C (что было бы вредно только в том случае, если конструкторы дорогие или есть затенение) . - person Necrolis; 06.07.2011
comment
@Necrolis Это правильно, я не пытаюсь выполнять вызовы перекрестных функций. - person BSchlinker; 06.07.2011

На заданный вами вопрос я рекомендую ответ @Nemo. Используйте возможности C++ для вызова деструкторов и придерживайтесь базовой лексической области видимости.

Надеюсь, вы знаете, что измерение времени выполнения имеет свою полезность, но это очень косвенный способ найти «узкие места». (Я предпочитаю «утечку времени». Программы медленные не потому, что у них есть узкие места, они медленные, потому что случайно делают гораздо больше, чем нужно.)

Вот еще немного по вопросам.

person Mike Dunlavey    schedule 06.07.2011