Получить количество циклов процессора?
Я видел это сообщение на SO, которое содержит C-код, чтобы получить последнее количество циклов процессора:
Профилирование количества циклов процессора на C / C ++ Linux x86_64
Есть ли способ, которым я могу использовать этот код в C ++ (приветствуются решения Windows и Linux)? Хотя написано в C (и C является подмножеством C ++), я не слишком уверен, что этот код будет работать в проекте C ++, а если нет, то как его перевести?
- Float vs Double Performance
- Производительность mgo-запросов кажется медленно медленной (500-650 мс)
- Производительность MongoDB по агрегационным запросам
- Как ускорить запуск Java VM (JVM)?
- Android: RunOnUiThread против AsyncTask
Я использую x86-64
EDIT2:
Обнаружил эту функцию, но не смог заставить VS2010 распознать ассемблер. Нужно ли включать что-нибудь? (По-моему, мне нужно поменять uint64_t
на long long
для окон ….?)
static inline uint64_t get_cycles() { uint64_t t; __asm volatile ("rdtsc" : "=A"(t)); return t; }
EDIT3:
Из кода выше я получаю сообщение об ошибке:
“ошибка C2400: синтаксическая ошибка встроенного ассемблера в ‘opcode’, найдена ‘тип данных’
Может ли кто-нибудь помочь?
- MySQL: самый быстрый способ подсчета количества строк
- В .NET, какой цикл работает быстрее, «for» или «foreach»?
- Нюансы NSMutableArray initWithCapacity
- Декодирование длины пробега в MATLAB
- Что лучше использовать в PHP $ array = $ value или array_push ($ array, $ value)?
- Что быстрее? ByVal или ByRef?
- Медленная загрузка первой страницы на сайт asp.net
- Есть ли причина в производительности для объявления параметров метода final в Java?
Начиная с GCC 4.5 и более поздних __rdtsc()
встроенный __rdtsc()
теперь поддерживается как MSVC, так и GCC.
Но в том числе нужно другое:
#ifdef _WIN32 #include #else #include #endif
Вот исходный ответ перед GCC 4.5.
Вытащил прямо из одного из моих проектов:
#include // Windows #ifdef _WIN32 #include uint64_t rdtsc(){ return __rdtsc(); } // Linux/GCC #else uint64_t rdtsc(){ unsigned int lo,hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return ((uint64_t)hi << 32) | lo; } #endif
VC ++ использует совершенно другой синтаксис для встроенной сборки, но только в 32-битных версиях. 64-разрядный компилятор не поддерживает встроенную сборку вообще.
В этом случае это, вероятно, так же хорошо – rdtsc
имеет (по крайней мере) две серьезные проблемы, когда речь заходит о временных кодовых последовательностях. Сначала (как и большинство инструкций) его можно выполнить не по порядку, поэтому, если вы пытаетесь выполнить короткую последовательность кода, rdtsc
до и после этого кода может быть rdtsc
до него или после него или что вы (я уверен, что они всегда будут выполняться по порядку друг относительно друга, поэтому, по крайней мере, разница никогда не будет отрицательной).
Во-вторых, в многоядерной (или многопроцессорной) системе один rdtsc может выполняться на одном ядре / процессоре, а другой – на другом ядре / процессоре. В этом случае отрицательный результат вполне возможен.
Вообще говоря, если вам нужен точный таймер под Windows, вам будет лучше использовать QueryPerformanceCounter
.
Если вы действительно настаиваете на использовании rdtsc
, я считаю, что вам придется делать это в отдельном модуле, написанном полностью на языке ассемблера (или использующем встроенный компилятор), а затем связан с вашим C или C ++. Я никогда не писал этот код для 64-битного режима, но в 32-битном режиме он выглядит примерно так:
xor eax, eax cpuid xor eax, eax cpuid xor eax, eax cpuid rdtsc ; save eax, edx ; code you're going to time goes here xor eax, eax cpuid rdtsc
Я знаю, что это выглядит странно, но на самом деле это правильно. Вы выполняете CPUID, потому что это команда сериализации (не может быть выполнена не в порядке) и доступна в пользовательском режиме. Вы выполняете его три раза, прежде чем начинать отсчет времени, потому что Intel документирует тот факт, что первое исполнение может / будет выполняться с другой скоростью, чем вторая (и то, что они рекомендуют, три, так что это три).
Затем вы выполняете свой тестируемый код, еще один cpuid для принудительной сериализации, а окончательный rdtsc получает время после завершения кода.
Наряду с этим вы хотите использовать любые средства, поставляемые вашей ОС, чтобы заставить все это работать на одном процессе / ядре. В большинстве случаев вы также хотите принудительно выравнивать код – изменения в выравнивании могут привести к довольно существенным различиям в исполнении.
Наконец, вы хотите выполнить его несколько раз – и всегда возможно, что он будет прерван в середине вещей (например, переключатель задачи), поэтому вам нужно быть готовым к возможности выполнения, дольше, чем остальные – например, 5 прогонов, которые занимают ~ 40-43 тактовых цикла за штуку, а шестой – 10000+ тактов. Ясно, что в последнем случае вы просто выбросите outlier – это не из вашего кода.
Резюме: управление выполнением самой инструкции rdtsc (почти) является наименьшим из ваших забот. Вам нужно сделать еще немного, прежде чем вы сможете получить результаты от rdtsc
, которые на самом деле означают что угодно.
Для Windows Visual Studio предоставляет удобный «встроенный компилятор» (то есть специальную функцию, которую понимает компилятор), которая выполняет инструкцию RDTSC для вас и возвращает результат:
unsigned __int64 __rdtsc(void);
Для этого вам не нужен встроенный asm . Нет никакой пользы; у компиляторов есть встроенные модули для rdtsc
и rdtscp
, и (по крайней мере в эти дни) все определяют внутренность __rdtsc
если вы включаете правильные заголовки. Но в отличие от почти всех других случаев ( https://gcc.gnu.org/wiki/DontUseInlineAsm ), нет серьезного недостатка для asm, если вы используете хорошую и безопасную реализацию, такую как @ Mysticial , а не со сломанным Ограничение "=A"
.
К сожалению, MSVC не согласен со всеми, о том, какой заголовок использовать для не-SIMD-функций.
Руководство _rdtsc
Intel говорит, что _rdtsc
(с одним подчеркиванием) находится в
, но это не работает на gcc и clang. Они определяют только свойства SIMD в
, поэтому мы придерживаемся
(MSVC) и
(все остальное, включая недавний ICC). Для совместимости с MSVC и документации Intel, gcc и clang определяют как однонаправленные, так и двухзначные версии функции.
Интересный факт: версия с двойным подчеркиванием возвращает неподписанное 64-битное целое число, в то время как Intel документирует _rdtsc()
как возвращаемый (подписанный) __int64
.
// valid C99 and C++ #include // is preferred in C++, but stdint.h works. #ifdef _MSC_VER # include #else # include #endif // optional wrapper if you don't want to just use __rdtsc() everywhere inline uint64_t readTSC() { // _mm_lfence(); // optionally wait for earlier insns to retire before reading the clock uint64_t tsc = __rdtsc(); // _mm_lfence(); // optionally block later instructions until rdtsc retires return tsc; } // requires a Nehalem or newer CPU. Not Core2 or earlier. IDK when AMD added it. inline uint64_t readTSCp() { unsigned dummy; return __rdtscp(&dummy); // waits for earlier insns to retire, but allows later to start }
Компилирует все 4 основных компилятора: gcc / clang / ICC / MSVC, для 32 или 64-разрядных. Посмотрите результаты в проводнике компилятора Godbolt , включая несколько тестовых абонентов.
Эти свойства были новыми в gcc4.5 (с 2010 года) и clang3.5 (с 2014 года) . gcc4.4 и clang 3.4 на Godbolt не компилируют это, но gcc4.5.3 (апрель 2011). Вы можете увидеть встроенный asm в старом коде, но вы можете и должны заменить его на __rdtsc()
. Компиляторы старше десяти лет обычно выполняют медленный код, чем gcc6, gcc7 или gcc8, и имеют менее полезные сообщения об ошибках.
Внутренний MSVC (я думаю) существовал гораздо дольше, потому что MSVC никогда не поддерживал встроенный asm для x86-64. ICC13 имеет __rdtsc
в immintrin.h
, но вообще не имеет x86intrin.h
. В недавнем ICC есть x86intrin.h
, по крайней мере, как Godbolt устанавливает их для Linux, которые они делают.
Вы можете определить их как long long
, особенно если вы хотите вычесть их и преобразовать в float. int64_t
-> float / double более эффективен, чем uint64_t
на x86 без AVX512. Кроме того, небольшие отрицательные результаты могут быть возможны из-за миграции ЦП, если TSC не идеально синхронизированы, и это, вероятно, имеет больше смысла, чем огромные числа без знака.
BTW, clang также имеет портативный __builtin_readcyclecounter()
который работает с любой архитектурой. (Всегда возвращает ноль на архитектуре без счетчика циклов.) См. Документы по языку расширения clang / LLVM
Для получения дополнительной информации об использовании lfence
(или cpuid
) для повышения повторяемости rdtsc
и управления точно, какие команды находятся / не находятся в rdtsc
интервале, заблокировав выполнение вне порядка , см. Ответ @HadiBrais на clflush, чтобы аннулировать строку кэша через C функции и комментарии для примера разницы, которую она делает.
См. Также Сериализация LFENCE на процессорах AMD? (TL: DR да с включенным смягчением Spectre, в противном случае ядра оставляют соответствующий MSR отключенным, поэтому вы должны использовать cpuid
для сериализации.) Это всегда было определено как частичное сериализация на Intel.
Как проверить сроки выполнения кода на Intel® IA-32 и IA-64 Instruction Set Architects , белый документ Intel с 2010 года.
rdtsc
подсчитывает опорные циклы, а не тактовые циклы ядра процессора
Он рассчитывается на фиксированной частоте независимо от турбо / энергосбережения, поэтому, если вы хотите провести анализ в режиме «часы-часы», используйте счетчики производительности. rdtsc
точно коррелирует с временем настенных часов (за исключением системных тактовых настроек, поэтому это идеальный источник времени для steady_clock
). Он указывает на частоту процессора, то есть рекламируемую частоту наклейки. (Или почти это, например, 2592 МГц на i7-6700HQ 2,6 ГГц Skylake.)
Если вы используете его для микромаркетинга, сначала включите период прогрева, чтобы убедиться, что ваш процессор уже находится на максимальной тактовой частоте, прежде чем вы начнете синхронизацию. (И, возможно, отключите турбо и сообщите операционной системе, чтобы она предпочитала максимальную тактовую частоту, чтобы избежать сдвига частоты процессора во время микрофотосъемки). Или лучше, используйте библиотеку, которая дает вам доступ к счетчикам производительности оборудования, или трюк, такой как perf stat для части программы, если ваша временная область достаточно длинная, чтобы вы могли прикрепить perf stat -p PID
.
Вы, как правило, по-прежнему хотите, чтобы часы процессора были исправлены для микрообъектов, хотя, если вы не хотите видеть, как разные нагрузки будут получать Skylake до тех пор, пока не будут связаны с памятью или что-то еще. (Обратите внимание, что пропускная способность / задержка в памяти в основном фиксирована с использованием разных часов, чем ядра. При простой частоте вращения прошивка кэша L2 или L3 занимает гораздо меньше тактовых циклов ядра.)
- Отрицательные измерения тактового цикла с обратной связью rdtsc? история RDTSC: изначально процессоры не делали энергосбережения, поэтому TSC был как в реальном времени, так и в ядрах. Затем он эволюционировал с помощью различных едва полезных шагов в свою текущую форму полезного источника с малыми затратами времени, отделяемого от основных тактовых циклов (
constant_tsc
), который не останавливается, когда часы останавливаются (nonstop_tsc
). Также некоторые советы, например, не занимают среднее время, принимают медианную (будут очень высокие выбросы). - std :: chrono :: часы, аппаратные часы и количество циклов
- Получение циклов процессора с использованием RDTSC – почему значение RDTSC всегда увеличивается?
- Потерянные циклы на Intel? Несоответствие между rdtsc и CPU_CLK_UNHALTED.REF_TSC
- измерение времени выполнения кода в C с использованием команды RDTSC содержит некоторые ошибки, включая SMI (прерывания системного управления), которых вы не можете избежать даже в режиме ядра с помощью
cli
) и виртуализациюrdtsc
под виртуальнойrdtsc
. И, конечно, основные вещи, такие как обычные прерывания, возможны, поэтому повторяйте свое время много раз и выбросите выбросы. -
Определите частоту TSC в Linux . Программно запросить частоту TSC сложно и, возможно, невозможно, особенно в пользовательском пространстве, или может дать худший результат, чем калибровка . Калибровка с использованием другого известного источника времени требует времени. См. Этот вопрос для получения дополнительной информации о том, как сложно преобразовать TSC в наносекунды (и было бы неплохо, если бы вы могли спросить ОС, что такое коэффициент преобразования, поскольку ОС уже делала это при загрузке).
Если вы настроили microbenchmarking с помощью RDTSC для настройки, лучше всего использовать клещи и пропустить даже попытку конвертировать в наносекунды. В противном случае используйте функцию времени библиотеки с высоким разрешением, такую как
std::chrono
clock_gettime
илиclock_gettime
. См. Более быстрый эквивалент gettimeofday для некоторого обсуждения / сравнения функций временной метки или чтения общей метки времени из памяти, чтобы избежатьrdtsc
полностью, если ваше требование точности достаточно низкое для прерывания таймера или streamа для его обновления.См. Также Расчет системного времени с использованием rdtsc об обнаружении частоты кристалла и множителя.
Также не гарантируется синхронизация TSC всех ядер . Поэтому, если ваш stream переносится на другое kernel процессора между __rdtsc()
, может возникнуть дополнительный перекос. (Большинство ОС пытаются синхронизировать TSC всех ядер, хотя, как правило, они будут очень близки.) Если вы используете rdtsc
напрямую, вы, вероятно, захотите rdtsc
свою программу или stream к ядру, например, с taskset -c 0 ./myprogram
для Linux.
Операция выбора ЦП TSC, особенно в многоядерной многопроцессорной среде, говорит о том, что Nehalem и новее синхронизируются и блокируются TSC для всех ядер в пакете (т. Е. Инвариантных TSC). Но многопроцессорные системы все еще могут быть проблемой. Даже более старые системы (например, до Core2 в 2007 году) могут иметь TSC, который останавливается, когда часы ядра останавливаются или привязаны к фактической тактовой частоте ядра вместо эталонных циклов. (Новые процессоры всегда имеют постоянный TSC и non-stop-TSC.) Дополнительную информацию см. В ответе @ amdn на этот вопрос.
Насколько хорош asm от использования встроенного?
Это примерно так же хорошо, как вы могли бы получить от встроенного asm в GNU C Mystical, или лучше, потому что он знает, что верхние разряды RAX обнуляются. Основной причиной, по которой вы хотите сохранить inline asm, является совместимость с жесткими старыми компиляторами.
Не встроенная версия функции readTSC
сама компилируется с MSVC для x86-64 следующим образом:
unsigned __int64 readTSC(void) PROC ; readTSC rdtsc shl rdx, 32 ; 00000020H or rax, rdx ret 0 ; return in RAX
Для 32-битных соглашений вызова, которые возвращают 64-разрядные целые числа в edx:eax
, это просто rdtsc
/ ret
. Не то чтобы это важно, вы всегда хотите, чтобы это было встроено.
В тестовом абоненте, который использует его дважды и вычитает во время интервала:
uint64_t time_something() { uint64_t start = readTSC(); // even when empty, back-to-back __rdtsc() don't optimize away return readTSC() - start; }
Все 4 компилятора делают очень похожий код. Это 32-разрядный вывод GCC:
# gcc8.2 -O3 -m32 time_something(): push ebx # save a call-preserved reg: 32-bit only has 3 scratch regs rdtsc mov ecx, eax mov ebx, edx # start in ebx:ecx # timed region (empty) rdtsc sub eax, ecx sbb edx, ebx # edx:eax -= ebx:ecx pop ebx ret # return value in edx:eax
Это вывод MSVC x86-64 (с применением имени-demangling). gcc / clang / ICC все испускают идентичный код.
# MSVC 19 2017 -Ox unsigned __int64 time_something(void) PROC ; time_something rdtsc shl rdx, 32 ; high <<= 32 or rax, rdx mov rcx, rax ; missed optimization: lea rcx, [rdx+rax] ; rcx = start ;; timed region (empty) rdtsc shl rdx, 32 or rax, rdx ; rax = end sub rax, rcx ; end -= start ret 0 unsigned __int64 time_something(void) ENDP ; time_something
Все 4 компилятора используют or
+ mov
вместо lea
чтобы объединить нижнюю и верхнюю половинки в другой регистр. Я думаю, что это своего рода консервированная последовательность, которую они не могут оптимизировать.
Но писать shift / lea inline asm самостоятельно вряд ли лучше. Вы лишили бы компилятор возможности игнорировать высокие 32 бита результата в EDX, если вы выбрали такой короткий интервал, чтобы сохранить только 32-битный результат. Или, если компилятор решает сохранить время начала в памяти, он может просто использовать два 32-битных хранилища вместо shift / or / mov. Если 1 дополнительный uop как часть вашего времени беспокоит вас, вы должны написать весь свой microbenchmark в чистом asm.
Однако мы можем, возможно, получить лучшее из обоих миров с модифицированной версией кода @ Mysticial:
// More efficient than __rdtsc() in some case, but maybe worse in others uint64_t rdtsc(){ // long and uintptr_t are 32-bit on the x32 ABI (32-bit pointers in 64-bit mode), so #ifdef would be better if we care about this trick there. unsigned long lo,hi; // let the compiler know that zero-extension to 64 bits isn't required __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return ((uint64_t)hi << 32) + lo; // + allows LEA or ADD instead of OR }
В Godbolt это иногда дает лучший asm, чем __rdtsc()
для gcc / clang / ICC, но в других случаях он компилирует трюки в использование дополнительного регистра для сохранения lo и hi отдельно, поэтому clang может оптимизироваться ((end_hi-start_hi)<<32) + (end_lo-start_lo)
. Надеюсь, если будет реальное давление в регистре, компиляторы будут объединяться раньше. (gcc и ICC все еще сохраняют lo / hi отдельно, но также не оптимизируются).
Но 32-разрядный gcc8 создает беспорядок, компилируя даже rdtsc()
функцию rdtsc()
с фактическим add/adc
с нулями вместо того, чтобы просто возвращать результат в edx: eax, как clang. (gcc6 и ранее выполняли ok с |
вместо +
, но определенно предпочитают __rdtsc()
если вам __rdtsc()
32-разрядный код-gen из gcc).