Intereting Posts
bsearch возвращает NULL, но элемент находится в массиве добавление элементов в связанный список без дублирования Преобразование символа ASCII в x11 keycode Отображение переменной с плавающей запятой в виде шестнадцатеричного целого числа сжимает соседнее целое число Есть ли разница между 1U и 1 в C? Cygwin1.dll «не найден» при запуске программы, написанной на C. Как я могу заставить Windows найти ее? Как преобразовать десятичные литералы в hex в C, 500 до 0x0500 Как напечатать имя символов файлов ELF, таких как nm? необходимо ли печатать malloc и calloc Какой алгоритм применяется для непрерывного перераспределения небольших fragmentов памяти? Использование strtol для проверки целочисленного ввода в ANSI C NaN как специальный аргумент Ошибка чтения файла XTC Такая же ошибка обнаруживается в сохраненной ** процедуре **, но не в сохраненной ** функции ** Что происходит, когда вы пишете в память за пределами массива?

Профилирование количества циклов процессора на C / C ++ Linux x86_64

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

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». Какие-либо предложения ?

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

  1. Поскольку я использую компилятор Watcom, я использовал rdtsc, используя «#pragma aux», что означает, что компилятор C будет генерировать инструкцию inline, ожидайте результата в edx: eax, а также сообщите его оптимизатору, что содержимое eax и edx было модифицирована. Это огромное улучшение от традиционных реализаций _asm, где оптимизатор будет избегать оптимизации в окрестности _asm. Я также реализовал divide_U8_by_U4, используя «#pragma aux», так что мне не нужно будет вызывать функцию lib при преобразовании clock_cycles нам или ms.
  2. Каждое выполнение rdtsc приведет к некоторым накладным расходам (LOT больше, если оно инкапсулировано, как в примере автора), которое должно быть более учтено, чем короче последовательность для измерения. Как правило, я не занимаю более коротких последовательностей, чем 1/30 внутренней тактовой частоты, которая обычно работает до 1/10 ^ 8 секунд (внутренние часы 3 ГГц). Я использую такие измерения, как показания, а не факт. Зная это, я могу отказаться от cpuid. Чем больше раз я измеряю, тем ближе к тому, что я получу.
  3. Чтобы надежно измерить, я бы использовал диапазон 1/100 – 1/300 i / e 0,03 – 0,1 us. В этом диапазоне дополнительная точность использования 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 в нас или ниже) увеличит риск получения исключения разделения в divide_U8_by_U4, поэтому я думаю, когда использовать нас и когда использовать ms.
  7. У меня также есть код для базовой статистики. Используя это значение I log min и max, я могу рассчитать среднее и стандартное отклонение. Этот код является нетривиальным, поэтому его собственный ET должен быть вычтен из измеренных ET.
  8. Если компилятор выполняет обширную оптимизацию, и ваши чтения хранятся в локальных переменных, компилятор может определить («правильно»), что код можно опустить. Один из способов избежать этого – сохранить результаты в публичных (нестатических, не стековых) переменных.
  9. Программы, работающие в реальных условиях, должны измеряться в реальных условиях, и это невозможно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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