Профилирование на основе количества циклов процессора в C / C ++ Linux x86_64 - PullRequest
5 голосов
/ 30 сентября 2010

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

static __inline__ unsigned long GetCC(void)
{
  unsigned a, d; 
  asm volatile("rdtsc" : "=a" (a), "=d" (d)); 
  return ((unsigned long)a) | (((unsigned long)d) << 32); 
}

Не думаю, что это лучше, поскольку даже два последовательных вызова дают мне разницуиз "33".Есть предложения?

Ответы [ 6 ]

6 голосов
/ 07 декабря 2010

Лично я считаю, что инструкция rdtsc хороша и может использоваться для самых разных задач.Я не думаю, что использование cpuid необходимо для подготовки к rdtsc.Вот как я обдумываю rdtsc:

  1. Поскольку я использую компилятор Watcom, я реализовал rdtsc, используя "#pragma aux", что означает, что компилятор C будет генерировать инструкцию inline, ожидайте результат вedx: eax, а также сообщить оптимизатору, что содержимое eax и edx было изменено.Это огромное улучшение по сравнению с традиционными реализациями _asm, когда оптимизатор будет избегать оптимизации в непосредственной близости от _asm.Я также реализовал div_U8_by_U4, используя "#pragma aux", так что мне не нужно вызывать функцию lib, когда я конвертирую clock_cycles в нас или в ms.
  2. Каждое выполнение rdtsc приведет к некоторым издержкам (Гораздо больше, если он инкапсулирован (как в примере автора), который должен быть принят во внимание, чем короче последовательность для измерения.Как правило, я не рассчитываю время более коротких последовательностей, чем 1/30 внутренней тактовой частоты, которая обычно составляет 1/10 ^ 8 секунд (внутренние часы 3 ГГц).Я использую такие измерения как показания, а не факт.Зная это, я могу опустить процессор.Чем больше я буду измерять, тем ближе к факту я получу.
  3. Для надежного измерения я бы использовал диапазон 1/100 - 1/300, то есть 0,03 - 0,1 мкс.В этом диапазоне дополнительная точность использования cpuid практически невелика.Я использую этот диапазон для синхронизации короткой последовательности.Это мой «нестандартный» блок, так как он зависит от внутренней тактовой частоты процессора.Например, на машине с тактовой частотой 1 ГГц я бы не использовал 0,03 нас, потому что это поставило бы меня за пределы 1/100, и мои показания стали бы показаниями.Здесь я бы использовал 0.1 us в качестве единицы измерения кратчайшего времени.1/300 не будет использоваться, поскольку он будет слишком близок к 1 мкс (см. Ниже), чтобы иметь какое-либо существенное различие.
  4. Для еще более длинных последовательностей обработки я делю разницу между двумя показаниями rdtsc, скажем, 3000 (для3 ГГц) и преобразует нам прошедшие такты.На самом деле я использую (diff + 1500) / 3000, где 1500 - половина 3000. Для ожидания ввода-вывода я использую миллисекунды => (diff + 1500000) / 3000000.Это мои "стандартные" единицы.Я очень редко использую секунды.
  5. Иногда я получаю неожиданно медленные результаты, и тогда я должен спросить себя: это из-за прерывания или из-за кода?Я измеряю еще несколько раз, чтобы увидеть, действительно ли это было прерывание.В этом случае ... ну, в реальном мире постоянно происходят прерывания.Если моя последовательность короткая, то есть вероятность, что следующее измерение не будет прервано.Если последовательность длиннее, прерывания будут происходить чаще, и я ничего не могу с этим поделать.
  6. Очень точное измерение длительности прошедшего времени (у нас час или больше ET или меньше) увеличит риск полученияисключение деления в div_U8_by_U4, поэтому я продумываю, когда использовать нас и когда использовать мс.
  7. У меня также есть код для базовой статистики.Используя это, я записываю минимальные и максимальные значения и могу вычислить среднее и стандартное отклонение.Этот код нетривиален, поэтому его собственный ET должен быть вычтен из измеренных ET.
  8. Если компилятор выполняет обширную оптимизацию и ваши показания хранятся в локальных переменных, компилятор может определить («правильно»), чтокод может быть опущен.Один из способов избежать этого - хранить результаты в открытых (нестатических, не на основе стека) переменных.
  9. Программы, работающие в реальных условиях, должны измеряться в реальных условиях, нет никакого способавокруг этого.

Что касается точности счетчика меток времени, я бы сказал, что при условии синхронизации tsc на разных ядрах (что является нормой) существует проблема дросселирования ЦП в периоды низкой активности для снижения энергопотребления.При тестировании всегда можно заблокировать функциональность.Если вы выполняете инструкцию на частоте 1 ГГц или 10 МГц на одном и том же процессоре, отсчет истекшего цикла будет таким же, даже если первый завершен за 1% времени по сравнению с последним.

2 голосов
/ 30 сентября 2010

Еще одна вещь, о которой вам может понадобиться беспокоиться, это то, что если вы работаете на многоядерном компьютере, программа может быть перенесена на другое ядро ​​с другим счетчиком rdtsc. Однако вы можете закрепить процесс на одном ядре с помощью системного вызова.

Если бы я пытался измерить что-то вроде этого, я, вероятно, записал бы метки времени в массив, а затем вернулся бы и исследовал этот массив после того, как был выполнен тестовый код. При изучении данных, записанных в массив временных отметок, вы должны иметь в виду, что этот массив будет полагаться на кэш-память ЦП (и, возможно, на пейджинг, если ваш массив большой), но вы могли бы предварительно извлечь или просто помнить об этом, анализируя данные. Вы должны видеть очень регулярную дельту времени между отметками времени, но с несколькими всплесками и, возможно, несколькими провалами (вероятно, из-за перемещения в другое ядро). Регулярная дельта времени, вероятно, является вашим лучшим измерением, поскольку предполагает, что никакие внешние события не повлияли на эти измерения.

При этом, если код, который вы тестируете, имеет нерегулярные шаблоны доступа к памяти или время выполнения или использует системные вызовы (особенно связанные с IO), то вам будет трудно отделить шум от интересующих вас данных.

2 голосов
/ 30 сентября 2010

Вы на правильном пути 1 , но вам нужно сделать две вещи:

  1. Выполнить инструкцию cpuid перед rdtsc, чтобы очистить конвейер ЦП (делаетизмерение более надежное).Насколько я помню это регистрируется клобберами от eax до edx.
  2. Измерение в реальном времени.Время выполнения намного больше, чем просто циклы ЦП (конкуренция за блокировку, переключение контекста и другие издержки, которые вы не контролируете).Калибруйте тики TSC в режиме реального времени.Вы можете сделать это в простом цикле, который принимает различия в измерениях, скажем, gettimeofday (Linux, так как вы не упомянули платформу) вызовов и rdtsc вывода.Затем вы можете сказать, сколько времени занимает каждый тик TSC.Другим соображением является синхронизация TSC между процессорами, поскольку каждое ядро ​​может иметь свой собственный счетчик.В Linux вы можете увидеть это в /proc/cpuinfo, ваш процессор должен иметь флаг constant_tsc.У большинства новых процессоров Intel, которые я видел, есть этот флаг.

1 У меня лично найдено rdtsc, чтобы быть более точным, чем системные вызовы типа gettimeofday() для мелкозернистых измерений.

2 голосов
/ 30 сентября 2010

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

Правильный путь - либо:

  • Подсчитайте количество циклов или процессорного времени (с clock()), затраченных на большое количество вызовов функции, затем усредните их; или
  • Используйте эмулирующий профилировщик на уровне цикла, например Callgrind / kcachegrind .

Кстати, перед серией RDTSC необходимо выполнить инструкцию по сериализации. Обычно используется CPUID.

1 голос
/ 30 сентября 2010

TSC не является хорошим показателем времени. Единственная гарантия, которую CPU дает для TSC, заключается в том, что он монотонно повышается (то есть, если вы RDTSC один раз, а затем сделаете это снова, второй вернет результат, который выше первого), и что он примет это очень долгое время , чтобы обернуть.

0 голосов
/ 30 сентября 2010

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

Я уверен, что вы знаете еще один хороший способ сделать это - просто зациклить другой код 10 ^ 6 раз, остановить его и назвать его микросекундами.

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

Если это так, то вы на правильном пути. Вы можете использовать такой инструмент, как Zoom или LTProf . Вот мой любимый метод.

...