62
Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64 и IA-32 Михаил Курносов E-mail: [email protected] WWW: www.mkurnosov.net Центр параллельных вычислительных технологий Сибирский государственный университет телекоммуникаций и информатики Новосибирск, 2014

Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Embed Size (px)

Citation preview

Page 1: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Использование Time-Stamp Counterдля измерения времени выполнения кода на процессорах с архитектурой Intel 64 и IA-32

Михаил Курносов

E-mail: [email protected]: www.mkurnosov.net

Центр параллельных вычислительных технологийСибирский государственный университет телекоммуникаций и информатикиНовосибирск, 2014

Page 2: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Подсистема Time-Stamp Counter (TSC)

■ Подсистема TSC в процессорах Intel 64 и IA-32 (Intel & AMD) предназначена для мониторинга относительного времени возникновения различных событий

■ Компоненты подсистемы TSC:

○ Флаг TSC поддержки счетчика процессором (CPUID.1:EDX.TSC[bit 4] = 1)

○ 64-битный регистр IA32_TIME_STAMP_COUNTER

○ Инструкции для чтения и записи TSC (RDTSC, RDTSCP, ...)

○ Флаг TSD контроля доступа к TSC (CR4.TSD[bit 2] = 0 | 1)

■ Intel 64 and IA-32 Architectures Software Developer’s Manual // Volume 3B: 17.13 Time-Stamp Counterhttp://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

■ AMD64 Architecture Programmer’s Manual // Volume 2: 13.2.4 Time-Stamp Counterhttp://developer.amd.com/resources/documentation-articles/developer-guides-manuals/

Page 3: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Подсистема Time-Stamp Counter (TSC)

■ Каждый логический процессор имеет свою подсистему TSC (флаг TSC, флаг TSD, MSR IA32_TIME_STAMP_COUNTER)

■ В многопроцессорной системе (SMP/NUMA) значения TSC логических процессоров могут отличаться

○ процессоры выполнили перезагрузку в разные моменты времени

○ разная процедура инициализации процессоров

Page 4: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Счетчик TSC (регистр IA32_TIME_STAMP_COUNTER)

■ IA32_TIME_STAMP_COUNTER - это моделезависимый 64-битный регистр (Model Specific Register, адрес 0x10)

■ Счетчик сбрасывается в 0 при каждой перезагрузке процессора

■ Гарантируется, что счетчик не переполнится в течении 10 лет с момента перезагрузки

■ Счетчик непрерывно увеличивается на 1 с каждым тактом системной шины (FSB, Intel QPI) - зависит от микроархитектуры процессора

http://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx

Page 5: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Алгоритмы увеличения TSC

■ Процессоры Pentium M (family [06H], models [09H, 0DH]), Pentium 4, Intel Xeon (family [0FH], models [00H, 01H, or 02H]), P6 family [*]

○ Счетчик TSC увеличивается с каждым внутренним тактом процессора

○ Внутренний такт процессора может регулироваться технологией Intel SpeedStep (состояния ACPI C0 + P-состояния SpeedStep)

○ Счетчик работает (увеличивается) даже если процессор остановлен инструкцией HLT (состояние ACPI С1) или по сигналу STPCLK# (состояние ACPI C2)

○ Увеличение TSC останавливается при переходе процессора в режим “глубокого сна” - состояние ACPI C3 низкого энергопотребления (Deep Sleep State)

[непостоянная скорость] [останавливаемый]

[*] Intel 64 and IA-32 Architectures Software Developer’s Manual // Volume 3B: 17.13 Time-Stamp Counter, http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

Page 6: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Алгоритмы увеличения TSC

■ Процессоры Pentium 4, Intel Xeon (family [0FH], models [03H and higher]), Intel Core Solo, Intel Core Duo (family [06H], model [0EH]), Intel Xeon 5100 series, Intel Core 2 Duo (family [06H], model [0FH]), Intel Core 2, Intel Xeon (family [06H], DisplayModel [17H]), Intel Atom (family [06H], DisplayModel [1CH])

○ Счетчик TSC увеличивается с постоянной скоростью (constant rate) во всех состояниях ACPI C0 + P-состояниях Intel SpeedStep, ACPI C1, C2

○ Увеличение TSC останавливается при переходе процессора в режим “глубокого сна” (состояние ACPI C3 низкого энергопотребления)

○ TSC можно использовать как источник времени (wall clock timer)

[постоянная скорость] [останавливаемый]

Page 7: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Управление питанием и частотой

Двухпроцессорный сервер Intel (системная плата Intel SR2520SAF)

frontend кластера Xeon32

Ноутбук Lenovo ThinkPad X230

Page 8: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Invariant TSC

■ В относительно новых процессорах реализована поддержка инвариантного TSC (Invariant TSC >= Intel Nehalem, AMD)

■ Счетчик инвариантного TSC увеличивается с постоянной скоростью во всех ACPI C- и P/T-состояниях (включая ACPI C3 “Deep Sleep State”)

■ Счетчик TSC можно использовать как источник времени (wall time clock) - работа с ним быстрее, чем доступ к таймерам ACPI и HPET

■ Проверка поддержки Invariant TSC: CPUID.80000007H:EDX[8] = 1

[постоянная скорость] [неостанавливаемый]

Page 9: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проверка доступности Invariant TSC

Проверка поддержки Invariant TSC: CPUID.80000007H:EDX[8] = 1

/* is_tsc_invariant: Returns 1 if TSC is invariant. */int is_tsc_invariant(){ uint32_t edx;

__asm__ __volatile__ ( "movl $0x80000007, %%eax\n" "cpuid\n" "movl %%edx, %0\n" : "=r" (edx) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ );

return edx & (1U << 8) ? 1 : 0;}

Page 10: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ В ядре Linux TSC характеризуется двумя аттрибутами:

○ constant_tsc - счетчик TSC увеличивается с постоянной скоростью

○ nonstop_tsc - счетчик не останавливается во всех ACPI C-состояниях

■ Invariant TSC = constant_tsc + nonstop_tsc

$ cat /proc/cpuinfo | grep tscflags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms

Поддержка TSC в ядре Linux

Page 11: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Поддержка TSC в ядре Linux■ linux/arch/x86/kernel/cpu/intel.c

■ linux/arch/x86/kernel/cpu/tsc.c

static void early_init_intel(struct cpuinfo_x86 *c){ ...

/* * c->x86_power is 8000_0007 edx. Bit 8 is TSC runs at constant rate * with P/T states and does not stop in deep C-states. * * It is also reliable across cores and sockets. (but not across * cabinets - we turn it off in that case explicitly.) */if (c->x86_power & (1 << 8)) {

set_cpu_cap(c, X86_FEATURE_CONSTANT_TSC);set_cpu_cap(c, X86_FEATURE_NONSTOP_TSC);if (!check_tsc_unstable())

set_sched_clock_stable();}

...}

Page 12: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Доступ к счетчику TSC

■ Чтение счетчика TSC

○ Инструкции RDTSC

○ Инструкция RDMSR (адрес IA32_TIME_STAMP_COUNTER: 0x10)

○ Инструкция RDTSCP

■ Запись в счетчик TSC

○ Инстуркция WRMSR (адрес 0x10)

Page 13: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Code

RDTSC

Code

Инструкция RDTSC (Read Time-Stamp Counter)

■ Инструкция RDTSC загружает 64-битное значение IA32_TIME_STAMP_COUNTER в регистры EDX:EAX

○ EDX - старшие 32 бита○ EAX - младшие 32 бита

■ RDTSC не приводит к сериализации выполнения инструкций (not a serializing instruction): не гарантирует ожидания завершения всех предыдущих инструкций перед чтением счетчика TSC

■ Следующие инструкции могут быть выполнены раньше чтения счетчика TSC (Out of order execution)

uint32_t high, low;__asm__ __volatile__ ( "rdtsc\n" "movl %%edx, %0\n" "movl %%eax, %1\n" : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered regs. */);uint64_t ticks = ((uint64_t)high << 32) | low;

Page 14: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Инструкция RDTSCP (Read TSC and Processor ID)

■ Инструкция RDTSCP загружает 64-битное значение IA32_TIME_STAMP_COUNTER в регистры EDX:EAX и 32-битное значение IA32_TSC_AUX в регистр ECX

■ IA32_TSC_AUX - моделезависимый регистр (адрес 0xc0000103), инициализируется ядром операционной системы и содержит номер логического процессора

■ Можно использовать IA32_TSC_AUX для определения миграции потока на другой процессор между двумя последовательными чтениями счетчика TSC

■ Поддержка RDTSCP (>= Intel Nehalem, AMD): CPUID.80000001H:EDX[27] = 1

■ RDTSCP гарантирует ожидания завершения всех предыдущих инструкций перед чтением счетчика TSC

■ Выполнение следующих инструкции может быть начато раньше чтение счетчика TSC

Page 15: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Измерение времени выполнения кода

■ Имеется фрагмент кода (функция), требуется измерить продолжительность его выполнения

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

t0 = get_time();

MEASURED_CODE();

t1 = get_time();

elapsed_time = t1 - t0;

■ Результаты измерений должны быть воспроизводимыми (повторный запуск измерений должен давать такие же результаты или близкие к ним)

■ Без воспроизводимости невозможно осуществлять оптимизацию кода: как понять, что стало причиной сокращения времени выполнения кода - оптимизация или это ошибка измерений?

Page 16: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Методика измерения времени выполнения кода1. Готовим систему к проведению измерений

○ настраиваем аппаратные подсистемы (настройки BIOS)○ параметры операционной системы и процесса, в котором будут осуществляться измерения

2. Выполняем разогревочный вызов измеряемого кода (Warmup)○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике

(при первом вызове может осуществляться отложенная инициализация и пр.)

3. Выполняем многократные запуски и собираем статистику о времени выполнения ○ Каждый запуск должен осуществляться в одних и тех же условиях

(входные массивы заполнены одними и теми же данными, входные данные отсутствуют/присутствуют в кеш-памяти процессора, …)

○ Выполняем измерения пока: ✓ относительная стандартная ошибка среднего времени выполнения (RSE) больше 5%

[опционально]✓ число выполненных измерений меньше максимально допустимого

Page 17: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Методика измерения времени выполнения кода4. Проводим статистическую обработку результатов измерений

○ Находим и отбрасываем промахи измерений (выбросы, outliers):например, 25% минимальных и максимальных значений результатов измерений [опционально]

○ Вычисляем оценку математического ожидания времени выполнения (mean) (медиану [опционально])

○ Вычисляем несмещенную оценку дисперсии времени выполнения (unbiased sample variance - Var)

○ Вычисляем стандартное отклонение (corrected sample standard deviation - StdDev)

○ Вычисляем стандартную ошибку среднего времени выполнения (standard error of the mean - StdErr)

○ Вычисляем относительную стандартную ошибку среднего времени выполнения (relative standard error of the mean - RSE)

○ Строим доверительные интервалы (confidence interval)

Page 18: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ Математическое ожидание времени выполнения (mean)

■ Несмещенная оценка дисперсии времени выполнения (unbiased sample variance - Var)

■ Стандартное отклонение (corrected sample standard deviation - StdDev)

■ Стандартная ошибка среднего времени выполнения (standard error of the mean - StdErr)■ Относительная стандартная ошибка среднего времени выполнения

(relative standard error of the mean - RSE)

Элементарная обработка результатов измерений

Page 19: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Элементарная обработка результатов измерений■ RSE показывает на сколько близко вычисленное среднее время выполнения

к истинному среднему времени выполнения (среднему генеральной совокупности)○ На практике хорошая точность RSE <= 5%

■ Оценка погрешности при большом числе измерений

○ С вероятностью 0,997 время выполнения лежит в интервале ○ С вероятностью 0,955 время выполнения лежит в интервале ○ С вероятностью 0,683 время выполнения лежит в интервале

■ Оценка погрешности при малом числе измерений (n < 10)○ Задаем требуемый уровень α доверительной вероятности (0.95; 0.99), из таблицы берем

значение коэффициента Стьюдента

Page 20: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ Стандартный подход: s2 может быть < 0, ошибка при вычислении корня

■ Метод B.P. Welford’аKnuth D. The Art of Computer Programming, Vol. 2, 3ed. (p. 232)

Вычисление стандартного отклонения (StdEv, StdDev)

Page 21: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Построение графиков и анализ результатов

Результат 100 запусков бенчмарка: на каждом запуске многократно измерялось время выполнения кода и формировалась статистика

Каждая точка “+“ - это среднее время

выполнение функции (результат запуска

бенчмарка)

Page 22: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Построение графиков и анализ результатов

Результат 100 запусков бенчмарка: на каждом запуске многократно измерялось время выполнения кода и формировалась статистика

Каждая точка “+“ - это среднее время

выполнение функции (результат запуска

бенчмарка)

Статистика по средним k

запусков позволят делать выводы

о воспроизводимости результатов

Page 23: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ На практике не удается добиться идеальной воспроизводимости результатов измерений○ Требуется статистическая обработка результатов

■ Факторы влияющие на точность измерений времени выполнения кода

○ Во время выполнения кода может произойти прерывание и переключение на его обработчик (таймер, сетевая активность) - недетерминированность

○ Переключение контекста и миграция потока на другой логический процессор (второе чтение TSC с него, TSC процессоров могут быть не синхронизированы - результат некорректный)

○ Измеряемый код использует кеш-память процессора (данных, инструкций) - это ресурс, который разделяется с другими потоками; при каждом измерении, с большой вероятностью, кеш-память будет находится в другом состоянии;разное количество попаданий в кеш (cache hit) - недетерминированность

Воспроизводимость результатов измерений

прерывания,сигналы

переключение контекста, миграция

кеш-память,виртуальная

память,конвейер

Page 24: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ Факторы влияющие на точность измерения времени выполнения кода

○ При включенной технологии Intel Hyper-Threading инструкции измеряемого кода и команды другого логического процессора разделяют один суперскалярный конвейер - недетерминированность

○ Подсистемы управления питанием и частотой процессора могут изменить скорость обновления TSC (Intel SpeedStep, Intel TurboBoost; AMD Cool'n'Quiet, AMD Turbo Core) - недетерминированность

○ Функционирование подсистемы управления частотой процессора (CPU Power Management), приводит к недетерминированному времени выполнения измеряемого кода - частота процессора становится стохастической величиной - недетерминированность

Воспроизводимость результатов измерений (2)

Page 25: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Подготовка системы к измерениям

■ 1) Настраиваем аппаратные подсистемы (BIOS)

○ Отключаем Intel Hyper-Threading○ Отключаем управление питанием (CPU Power Management, Intel SpeedStep, AMD Cool'n'Quiet)○ Отключаем управление частотой (Intel TurboBoost, AMD Turbo Core)○ Отключаем System Management Interrupts (SMI) [по возможности]○ Отключаем NUMA Memory Node Interleaving [опционально - зависит от целей теста]

■ Загружаем систему в runlevel 3 (без X Window System)

○ В случае отсутствия инвариантного TSC загружаем ядро с параметрами [*]:processor.max_cstate=1, idle=poll (можно использовать подход с захватом /dev/cpu_dma_latency)

[*] Red Hat Enterprise MRG 2: Realtime Tuning Guide // https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_MRG/2/html/Realtime_Tuning_Guide/index.html

Page 26: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Подготовка системы к измерениям■ 2) Минимизируем вероятность прерывания измеряемого кода

○ User-space

✓ Отключаем службу irqbalance(освобождаем одно ядро от обработки прерываний для измерений, например последнее)

✓ Привязываем поток к процессору (sched_setaffinity(), taskset, numactl)

✓ Переводим поток в класс realtime-задач (sched_setscheduler: максимальный приоритет + SCHED_FIFO; nice)

○ Kernel-space (создаем модуль ядра, в нем проводим измерения)

✓ Запрещаем аппаратные прерывания (raw_local_irq_save())

✓ Запрещаем вытеснение процесса (preempt_disable(), get_cpu()/put_cpu())

Page 27: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Подготовка системы к измерениям■ 3) Настраиваем подсистемы, которые используются измеряемым кодом

○ На NUMA-системах (AMD + Hyper-Transport, Intel QPI) настраиваем политику выделения памяти (set_mempolicy(), numa_set_membind(); numactl):

✓ только с локального NUMA-узла

✓ c удаленного NUMA-узла

○ Запрещаем подкачку страниц (paging, swapping: mlockall()/munlockall())

○ Организуем разогрев/сброс кеш-памяти различных уровней перед измерениями

Page 28: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Выполнение измерений

■ 4) Инициализируем подсистему TSC и измеряем накладные расходы на доступ к TSC

■ 5) Выполняем разогревочный вызов измеряемого кода (Warmup)○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике

(при первом вызове может осуществляться отложенная инициализация и пр.)

■ 6) Выполняем многократные запуски и собираем статистику о времени выполнения

○ Каждый запуск должен осуществляться в одних и тех же условиях: ✓ входные массивы заполнены одними и теми же данными✓ входные данные отсутствуют/присутствуют в кеш-памяти процессора✓ …

○ Выполняем измерения пока:✓ относительная стандартная ошибка среднего времени выполнения (RSE) > 5% [опционально]✓ число выполненных измерений меньше максимально допустимого

■ 7) Проводим статистическую обработку результатов измерений

Page 29: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Стандартная схема использования TSC1 uint32_t high, low;2 __asm__ __volatile__ (3 "rdtsc\n" 4 "movl %%edx, %0\n"5 "movl %%eax, %1\n"6 : "=r" (high), "=r" (low)7 :: "%rax", "%rbx", "%rcx", "%rdx"8 );9 uint64_t ticks = ((uint64_t)high << 32) | low;

10 MEASURED_CODE();

11 __asm__ __volatile__ (12 "rdtsc\n" 13 "movl %%edx, %0\n"14 "movl %%eax, %1\n"15 : "=r" (high), "=r" (low)16 :: "%rax", "%rbx", "%rcx", "%rdx"17 );18 ticks = (((uint64_t)high << 32) | low) - ticks; 19 printf("Elapsed ticks: %" PRIu64 "\n", ticks);

2) Вызов тестируемой функции

1) Первое обращение к TSC (RDTSC)

3) Второе обращение к TSC

Page 30: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проблемы стандартной схемы

■ Внеочередное выполнение команд (Out of Order Execution)○ Инструкции стоящие до команды RDTSC могут быть выполнены после неё○ Инструкции стоящие после RDTSC могут быть выполнены до неё

■ Влияние других потоков и внешних событий○ В ходе измерений может произойти переключение контекста, миграция на другой логический

процессор (второе чтение TSC будет выполнено с другого процессора)○ При включенной технологии Intel Hyper-Threading в измеряемый код могут попасть

инструкции другого аппаратного потока (логические процессоры H-T разделяют один суперскалярный конвейер ядра)

○ В ходе измерений может быть вызван обработчик прерывания

■ Изменение частоты процессора и обновления TSC○ Частота обновления TSC может быть изменена подсистемами управления питанием и

частотой процессора (Intel SpeedStep, Intel TurboBoost; AMD Cool'n'Quiet, AMD Turbo Core)

+/- ticks+/- ticks

+ ticks

+ ticks

Page 31: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ Внеочередное выполнение команд (Out of Order Execution)○ Инструкции стоящие до RDTSC могут быть выполнены после неё○ Инструкции стоящие после RDTSC могут быть выполнены до неё

■ Необходимо предотвратить выполнение инструкций измеряемого кодавне “окна” чтения TSC

Проблемы: внеочередное выполнение команд

RDTSC

Code

RDTSC

Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: 8.2 Memory ordering

■ Reads are not reordered with other reads■ Writes are not reordered with older reads■ Reads may be reordered with older writes

to different locations but not with older writes to the same location

■ …

AMD64 Architecture Programmer’s Manual Volume 2: 7.2 Multiprocessor Memory Access Ordering

/* Code before */

uint32_t high, low; __asm__ __volatile__ ( "rdtsc\n" "movl %%edx, %0\n" "movl %%eax, %1\n" : "=r" (high), "=r" (low) :: "%rax", "%rbx", "%rcx", "%rdx" ); uint64_t ticks = ((uint64_t)high << 32) | low;

/* Code after */

Page 32: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Сериализующие инструкции и барьеры памяти

■ Сериализующие инструкции (Serializing instructions) организуют ожидание завершения модификации флагов, регистров и памяти предыдущими инструкциями перед выполнением следующей команды○ Превелигированные инструкции: INVD, INVEPT, INVLPG, INVVPID, LGDT, LIDT, ...○ Непривелигированные инструкции серилизации: CPUID, IRET, RSM

■ Инструкция RDTSCP○ Гарантирует ожидание завершения всех предыдуших операций○ Cледующие операции могут быть выполнены раньше команды RDTSCP

■ Процессоры Intel: LFENCE + RDTSC○ “If software requires RDTSC to be executed only after all previous instructions have completed locally,

it can either use RDTSCP (if the processor supports that instruction) or execute the sequence LFENCE;RDTSC”

■ Процессоры AMD: инструкция MFENCE○ “The following are serializing instructions: Non-Privileged Instructions: CPUID, IRET, RSM, MFENCE”

Page 33: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проблемы: внеочередное выполнение команд■ Решение 1: ожидаем завершения предыдущих операций перед

каждым чтением значения TSC (барьер, сериализация)○ Плюсы: относительно низкие накладные расходы

на сериализацию

○ Минусы: операции после чтения TSC могут быть выполнены раньше

■ How to Benchmark Code Execution Times on Intel IA-32 and IA-64 Instruction Set Architectures // Intel White Paper, 2010, http://www.intel.ru/content/www/ru/ru/intelligent-systems/embedded-systems-training/ia-32-ia-64-benchmark-code-execution-paper.html

■ Using the RDTSC Instruction for Performance Monitoring // Intel, 1997,https://www.ccsl.carleton.ca/~jamuir/rdtscpm1.pdf

■ Agner Fog. Test programs for measuring clock cycles and performance monitoring // http://www.agner.org/optimize/#testp

■ Linux kernel // arch/x86/lib/delay.c, arch/x86/kernel/tsc_sync.c

tsc_barrier()

read_tsc()

code()

tsc_barrier()

read_tsc()

Page 34: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проблемы: внеочередное выполнение команд■ Решение 2: выставляем барьер перед и после каждого чтения

значения TSC○ Плюсы: измеряемый код выполняется в исходном порядке

(в программном)

○ Минусы: относительно высокие накладные расходы на сериализацию

■ How to Benchmark Code Execution Times on Intel IA-32 and IA-64 Instruction Set Architectures // Intel White Paper, 2010, http://www.intel.ru/content/www/ru/ru/intelligent-systems/embedded-systems-training/ia-32-ia-64-benchmark-code-execution-paper.html

■ Linux kernel // arch/x86/kernel/tsc_sync.c

tsc_barrier()

read_tsc()

tsc_barrier()

code()

tsc_barrier()

read_tsc()

tsc_barrier()

Page 35: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Решения с одним барьером при чтении TSC

Barrier

cpuid {l,m}fence rdtscp

rdtsc

CODE

Barrier

cpuid {l,m}fence rdtscp

rdtsc

cpuidrdtsccodecpuidrdtsc

1) Инструкции измеряемого кода “не выскакивают” за пределы cpuid (могут выскочить за первый RDTSC)

2) Перед вторым RDTSC могут быть выполнены следующие за ним инструкции (но после cpuid)

rdtscpcoderdtscp

1) Инструкции измеряемого кода могут быть выполнены до первого RDTSCP

2) Перед вторым RDTSCP могут быть выполнены следующие за ним инструкции

1) Только инструкции LOAD измеряемого кода “не выскакивают” за пределы lfence (могут выскочить за первый RDTSC), инструкции другого типа могут быть выполнены до lfence

2) Перед вторым lfence могут быть выполнены следующие инструкции отличные от команд LOAD; перед RDTSC могут быть выполнены следующие за ним инструкции

Только инструкции LOAD/STORE измеряемого кода “не выскакивают” за пределы mfence

На процессорах AMD mfence - инструкция сериализации (как CPUID)

lfencerdtsccodelfencerdtsc

mfencerdtsccodemfencerdtsc

Page 36: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Решения с двумя барьерами при чтении TSCcpuidrdtsccpuidcodecpuidrdtsccpuid

Инструкции измеряемого кода “не выскакивают” за пределы второго и третьего cpuid

lfencerdtscmfencecode

mfencerdtscmfence

Процессоры Intel: инструкции LOAD и STORE измеряемого кода “не выскакивают” за пределы второго и третьего mfence

Процессоры AMD: инструкции измеряемого кода “не выскакивают” за пределы второго и третьего mfence

rdtscpmfencecode

rdtscpmfence

Процессоры Intel: инструкции LOAD и STORE измеряемого кода “не выскакивают” за пределы первого и второго mfence

Процессоры AMD: инструкции измеряемого кода “не выскакивают” за пределы первого и второго mfence

mfencerdtsccpuidcodecpuidrdtscmfence

Intel & AMD CPU’s

mfence - барьер памяти для LOAD и STORE операций, после него выполняется чтение TSC

Инструкции измеряемого кода “не выскакивают” за пределы cpuid

mfencerdtscmfencecodemfencerdtscmfence

rdtscpcpuidcode

rdtscpcpuid

lfencerdtsccpuidcode

lfencerdtsccpuid

AMD CPU’s

rdtscpmfencecoderdtscpmfence

Page 37: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Какое решение использовать?

cpuidrdtsccpuidcodecpuidrdtsccpuid

cpuidrdtsccodecpuidrdtsc

cpuidrdtsccodelfencerdtsccpuid

■ mfence - разная реализация для Intel и AMD

■ rdtscp - только в относительно “свежих” процессорах

■ lfence + rdtsc - гарантия упорядоченности только для операций LOAD

■ cpuid - поддерживатся большинством процессоров (высокая латентность)

■ Остановимся на следующих подходах

cpuidrdtsccodecpuidrdtscmfence

Std_mfence

cpuidrdtsccoderdtscpcpuid

Std Intel_howto Four_cpuidStd_lfence

read_tsc_before()

read_tsc_after()

Page 38: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ На реализацию барьеров (сериализацию) требуется время, которое вносит ошибку в измерения

■ Перед измерениями необходимо оценить время, требуемое для организации барьеров, и вычесть его из результирующего времени выполнения кода

Учет накладных расходов на сериализацию

/* Measure overhead */

uint64_t overhead = measure_tsc_overhead();

/* Measure code execution time */

uint64_t t0 = read_tsc_before();

CODE();

uint64_t t1 = read_tsc_after();

uint64_t ticks = (t1 > t0 && t1 - t0 > overhead) ? t1 - t0 - overhead : 0;

Page 39: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Измерение времени доступа к TSC/* * measure_tsc_overhead: Measures and returns minimal overhead for TSC reading. */uint64_t measure_tsc_overhead(){ enum { NMEASURES = 100 }; volatile uint64_t t0, t1, ticks, minticks = (uint64_t)~0x1;

for (int i = 0; i < NMEASURES; ) { t0 = read_tsc_before(); t1 = read_tsc_after(); if (t1 > t0) { ticks = t1 - t0; if (ticks < minticks) minticks = ticks; i++; } } return minticks;}

Вычисляем время доступа к TSC (overhead) как минимальное значение

по результатам нескольких запусков

Page 40: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Измерение времени доступа к TSCuint64_t measure_tsc_overhead_stabilized(){ enum { NOTCHANGED_THRESHOLD = 10, NMEASURES_MAX = 100 }; volatile uint64_t t0, t1, ticks, minticks = (uint64_t)~0x1; int notchanged = 0; for (int i = 0; i < NMEASURES_MAX && notchanged < NOTCHANGED_THRESHOLD; ) { t0 = read_tsc_before(); t1 = read_tsc_after(); if (t1 > t0) { ticks = t1 - t0; notchanged++; if (ticks < minticks) { minticks = ticks; notchanged = 0; } i++; } } return minticks;}

Время доступа к TSC вычисляем как минимальное значение, которое не менялось

последние k запусков (стабилизировалось)

Page 41: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Измерение времени доступа к TSC

Время доступа к TSC вычисляем с заданной точностью:

Relative Standard Error (RSE) < 5%

uint64_t measure_tsc_overhead_rse();{ ...

int nruns = NRUNS_MIN; do { stat_sample_clean(stat); for (int i = 0; i < nruns; ) { t0 = read_tsc_before(); t1 = read_tsc_after(); /* Accumulate only correct results */ if (t1 > t0) { stat_sample_add(stat, (double)(t1 - t0)); i++; } } /* Reduce measurement error by increasing number of runs */ nruns *= 4; } while (stat_sample_size(stat) < NRUNS_MAX && stat_sample_rel_stderr_knuth(stat) > RSE_MAX);

...

}

Page 42: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()

■ Сервер 2 x Intel Xeon E5620 (4 ядра, Invariant TSC, Intel HyperThreading включен) -- frontend кластера Oak

■ CentOS 6.5 (Linux 2.6.32-431.3.1.el6.x86_64 #1 SMP; runlevel 3)

■ GCC 4.4.7

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру второго процессора (irqbalance включен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)

Хорошая воспроизводимость

Mean ~ 96StdDev ~ 0.4RSE < 0.04 %

mean + 3 * sigma

Mean ~ 98.1StdDev ~ 0.26RSE < 0.03 %

Page 43: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()(RSE < 5%)

■ Сервер 2 x Intel Xeon E5420 (4 ядра, constant TSC only) -- frontend кластера Jet

■ Fedora 20 (3.11.10-301.fc20.x86_64; runlevel 3)

■ GCC 4.8.2

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру второго процессора (irqbalance включен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)

Хорошая воспроизводимость

Mean ~ 234.6StdDev ~ 0.5RSE < 0.02 %

Page 44: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()(RSE < 5%)

■ Ноутбук с процессором Intel Core i5-3320M (2 ядра, Invariant TSC, Intel HyperThreading отключен, включены Intel SpeedStep и CPU Power Management)

■ Fedora 20 (Linux 3.14.7-200.fc20.x86_64; GCC 4.8.2; runlevel 5: graphical.target - GNOME Shell)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру процессора (irqbalance отключен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)Удовлетворительная воспроизводимость

Mean ~ 88.7StdDev ~ 6.82

RSE < 0.8 %

mean + 3 * sigmaMean ~ 92.6

StdDev ~ 4.89RSE < 0.5 %

Mean ~ 97.3StdDev ~ 1.58

RSE < 0.2 %

Page 45: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()(RSE < 5%)

■ Ноутбук с процессором Intel Core i5-3320M (2 ядра, Invariant TSC, Intel HyperThreading отключен, выключены Intel SpeedStep и CPU Power Management)

■ Fedora 20 (Linux 3.14.7-200.fc20.x86_64; GCC 4.8.2; runlevel 5: graphical.target - GNOME Shell)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру процессора (irqbalance отключен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)

Mean ~ 115.4StdDev ~ 4.09

RSE < 0.4 %

Удовлетворительная воспроизводимость

Mean ~ 111.6StdDev ~ 8.3RSE < 0.8 %

Mean ~ 117StdDev ~ 2.2RSE < 0.2 %

Page 46: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()(RSE < 5%)

■ Ноутбук с процессором Intel Core i5-3320M (2 ядра, Invariant TSC, Intel HyperThreading отключен, выключены Intel SpeedStep и CPU Power Management)

■ Fedora 20 (Linux 3.14.7-200.fc20.x86_64; GCC 4.8.2; runlevel 3)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру процессора (irqbalance отключен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)

Хорошая воспроизводимость

Mean ~ 116StdDev ~ 0.0

RSE = 0 %

Mean ~ 116.7StdDev ~ 1.01

RSE < 0.1 %

mean + 3 * sigma

Page 47: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC

measure_tsc_overhead() measure_tsc_overhead_stabilized() measure_tsc_overhead_rse()(RSE < 5%)

■ Ноутбук с процессором Intel Core i5-3320M (2 ядра, Invariant TSC, Intel HyperThreading отключен, включены Intel SpeedStep и CPU Power Management)

■ Fedora 20 (Linux 3.14.7-200.fc20.x86_64; GCC 4.8.2; runlevel 3)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру процессора (irqbalance отключен)

■ Чтение TSC выполнялось методом Std (cpuid + rdtsc; cpuid + rdtsc)Удовлетворительная воспроизводимость

Page 48: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Время доступа к TSC различными методами

■ Ноутбук с процессором Intel Core i5-3320M (2 ядра, Invariant TSC, Intel HyperThreading off, выключены Intel SpeedStep и CPU Power Management)

■ Fedora 20 (Linux 3.14.7-200.fc20.x86_64; runlevel 3)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру процессора (irqbalance отключен)

■ Методы доступа к TSC (реализации read_tsc_before(), read_tsc_after()):

cpuidrdtsccpuidcodecpuidrdtsccpuid

cpuidrdtsccodecpuidrdtsc

cpuidrdtsccodelfencerdtsccpuid

cpuidrdtsccodecpuidrdtscmfence

Std_mfence

cpuidrdtsccoderdtscpcpuid

Std Intel_howto Four_cpuidStd_lfence

Page 49: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Воспроизводимость измерений (тест Primes)

Std

Std_mfence

Intel_howto Std_lfence

Four_cpuid

Mean ~ 12 972 457StdDev ~ 3849RSE < 0.003 %

Mean ~ 12 973 034StdDev ~ 4242RSE < 0.003 %

Mean ~ 12 971 891StdDev ~ 3169RSE < 0.002 %

Mean ~ 12 971 669StdDev ~ 3760RSE < 0.003 %

Mean ~ 12 930 873StdDev ~ 4321RSE < 0.003 %

Тест primes(подсчет количества простых чисел

в заданном интервале)

■ Сервер 2 x Intel Xeon E5620 (4 ядра, Invariant TSC, Intel HyperThreading off)

■ CentOS 6.5 (Linux 2.6.32-431.3.1.el6.x86_64; runlevel 3)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру второго процессора (irqbalance включен)

■ Измерение времени доступ к TSC выполнялось методом measure_tsc_overhead()

Page 50: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Воспроизводимость измерений (тест SAXPY)

Std

Std_mfence

Intel_howto Std_lfence

Four_cpuid

RSE < 0.012 % RSE < 0.007 %

RSE < 0.008 %Тест SAXPY (BLAS Level 1)(умножение элементов одного вектора на скаляр и поэлементное сложении со вторым вектором)

■ Сервер 2 x Intel Xeon E5620 (4 ядра, Invariant TSC, Intel HyperThreading off)

■ CentOS 6.5 (Linux 2.6.32-431.3.1.el6.x86_64; runlevel 3)

■ Тест запускался с правами непривилегированного пользователя с привязкой к последнему ядру второго процессора (irqbalance включен)

■ Измерение времени доступ к TSC выполнялось методом measure_tsc_overhead()

RSE < 0.009 %

RSE < 0.01 %

Page 51: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

■ Измерение времени доступа к TSC○ Вычисляем время доступа к TSC (overhead) как минимальное значение по результатам

нескольких запусков - функция measure_tsc_overhead()

■ Метод чтения TSC

○ При измерении времени выполнения фрагмента кода с большим числом операций выбор способа чтения TSC (Std, Intel_howto, Std_lfence, Std_mfence, Four_cpuid) существенно на результатах не сказывается -- используем Std, Intel_howto, Std_lfence

○ При измерении времени выполнения небольших участков кода (десятки инструкций) необходимо учитывать возможное внеочередное выполнение команд; желательно провести чтение TSC разными способами и проверить воспроизводимость результатов

Выводы

Page 52: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBenchint main(){ if (!is_tsc_available()) { fprintf(stderr, "# Error: TSC is not supported by this processor\n"); exit(1); }

prepare_system_for_benchmarking(); run_benchmark();

return 0;}

$ git clone https://github.com/mkurnosov/tscbench.git

Page 53: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBenchvoid prepare_system_for_benchmarking(){ /* Only ROOT can change scheduling policy to RT class */ if (geteuid() == 0) { struct sched_param sp; memset(&sp, 0, sizeof(sp)); sp.sched_priority = sched_get_priority_max(SCHED_FIFO); if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) fprintf(stderr, "# [Warning!] Error changing scheduling policy to RT class\n"); else printf("# Scheduling policy is changed to RT class with max priority\n"); /* Disable paging to swap area */ if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) fprintf(stderr, "# [Warning!] Error locking pages\n"); else printf("# All pages of process are locked (paging is disabled)\n");

} else { fprintf(stderr, "# [Warning!] Benchmark is launched without ROOT permissions:\n" "# default scheduler, default priority, pages are not locked\n"); }}

Page 54: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBenchvoid run_benchmark(){ #define RSE_MAX 5.0 enum { NRUNS_MIN = 100, NRUNS_MAX = 1000000 }; /* Measure TSC overhead */ uint64_t overhead = measure_tsc_overhead();

/* Warmup code (first run) */ volatile uint64_t t0 = read_tsc_before(); CODE(); volatile uint64_t t1 = read_tsc_after(); uint64_t firstrun = normolize_ticks(t0, t1, overhead);

stat_sample_t *stat = stat_sample_create(); if (stat == NULL) { fprintf(stderr, "# No enough memory for statistics"); exit(1); }

int nruns = NRUNS_MIN;

do { stat_sample_clean(stat); for (int i = 0; i < nruns; ) { t0 = read_tsc_before(); CODE(); t1 = read_tsc_after(); /* Accumulate only correct results */ if (t1 > t0) { if (t1 - t0 > overhead) { stat_sample_add(stat, (double)(t1 - t0 - overhead)); i++; } } } /* * Reduce measurement error by increasing * number of runs: StdErr = StdDev / sqrt(n) */ nruns *= 4; } while (stat_sample_size(stat) < NRUNS_MAX && stat_sample_rel_stderr_knuth(stat) > RSE_MAX);

Page 55: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBench printf("# Execution time statistic (ticks)\n"); printf("# TSC overhead (ticks): %" PRIu64 "\n", overhead); printf("# [Runs] [First run] [Mean] [StdDev] [StdErr] [RSE] [Min] [Max]\n");

printf(" %-6d %-18" PRIu64 " %-18.2f %-18.2f %-18.2f %-8.2f %-18.2f %-18.2f\n", stat_sample_size(stat), firstrun, stat_sample_mean_knuth(stat), stat_sample_stddev_knuth(stat), stat_sample_stderr_knuth(stat), stat_sample_rel_stderr_knuth(stat), stat_sample_min(stat), stat_sample_max(stat)); stat_sample_free(stat);

} /* run_benchmark() */

$ LASTCPU=`cat /proc/cpuinfo | grep processor | tail -n1 | cut -d':' -f2`$ numactl --physcpubind="$LASTCPU" --localalloc ./tscbench

# [Warning!] Benchmark is launched without ROOT permissions:# default scheduler, default priority, pages are not locked# Execution time statistic (ticks)# TSC overhead (ticks): 188# [Runs] [First run] [Mean] [StdDev] [StdErr] [RSE] [Min] [Max] 100 106762 82214.76 4754.10 475.41 0.58 79776.00 113744.00

Page 56: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBenchstatic inline uint64_t read_tsc_before_std(){ register uint32_t high, low; /* * 1. Prevent out-of-order execution (serializing by CPUID(0)): * wait for the completion of all previous operations (before measured code) * 2. Read TSC value */ __asm__ __volatile__ ( "xorl %%eax, %%eax\n" "cpuid\n" /* Serialize execution */ "rdtsc\n" /* Read TSC */ "movl %%edx, %0\n" /* - high 32 bits */ "movl %%eax, %1\n" /* - low 32 bits */ : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ ); return ((uint64_t)high << 32) | low;}

Page 57: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Исходный код TSCBenchstatic inline uint64_t read_tsc_after_std(){ register uint32_t high, low; /* * 1. Serialize by CPUID(0): wait for the completion of all operations in measured block * 2. Read TSC value */ __asm__ __volatile__ ( "xorl %%eax, %%eax\n" "cpuid\n" /* Serialize: wait for all prev. ops */ "rdtsc\n" /* Read TSC */ "movl %%edx, %0\n" /* - high 32 bits */ "movl %%eax, %1\n" /* - low 32 bits */ : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ ); return ((uint64_t)high << 32) | low;}

Page 58: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проверка миграции потока на другой процессорvoid check_migration(){ register uint32_t high0, low0, high1, low1, tscaux0, tscaux1; __asm__ __volatile__ ( "xorl %%eax, %%eax\n" "cpuid\n" "rdtscp\n" /* Загружаем IA32_TIME_STAMP_COUNTER и IA32_TSC_AUX в регистры */ "movl %%edx, %0\n" "movl %%eax, %1\n" "movl %%ecx, %2\n" /* IA32_TSC_AUX MSR */ : "=r" (high0), "=r" (low0), "=r" (tscaux0) :: "%rax", "%rbx", "%rcx", "%rdx" ); /* Measured code */ __asm__ __volatile__ ( "rdtscp\n" "movl %%edx, %0\n" "movl %%eax, %1\n" "movl %%ecx, %2\n" "xorl %%eax, %%eax\n" "cpuid\n" : "=r" (high1), "=r" (low1), "=r" (tscaux1) :: "%rax", "%rbx", "%rcx", "%rdx" );

printf("First RDTSCP: IA32_TSC_AUX = %" PRIu32 "\n", tscaux0); printf("Second RDTSCP: IA32_TSC_AUX = %" PRIu32 "\n", tscaux1); if (tscaux0 != tscaux1) fprintf(stderr, "Migration is occurred - second value of TSC was obtained from another CPU\n");}

$ ./testsFirst RDTSCP: IA32_TSC_AUX = 0Second RDTSCP: IA32_TSC_AUX = 0

Page 59: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Проверка миграции потока на другой процессор (2)void check_migration_cpuid(){ register uint32_t high0, low0, high1, low1, tscaux0, tscaux1, cpuid0, cpuid1;

__asm__ __volatile__ ( "movl $0x0b, %%eax\n" /* EAX=0x0b - information about processor */ "cpuid\n" "movl %%edx, %3\n" /* x2APIC ID the current logical processor */ "rdtscp\n" "movl %%edx, %0\n" "movl %%eax, %1\n" "movl %%ecx, %2\n" /* IA32_TSC_AUX MSR */

: "=r" (high0), "=r" (low0), "=r" (tscaux0), "=r" (cpuid0) :: "%rax", "%rbx", "%rcx", "%rdx" );

/* Migrate process to another processor */ cpu_set_t set; sched_getaffinity(0, sizeof(set), &set); int ncpus = CPU_COUNT(&set); for (int cpu = 0; cpu < ncpus; cpu++) CPU_SET(cpu, &set); CPU_CLR(tscaux0, &set); if (sched_setaffinity(0, sizeof(set), &set) != 0) fprintf(stderr, "Error changing processor affinity\n");

__asm__ __volatile__ ( "rdtscp\n" "movl %%edx, %0\n" "movl %%eax, %1\n" "movl %%ecx, %2\n" /* IA32_TSC_AUX MSR */ "movl $0x0b, %%eax\n" "cpuid\n" "movl %%edx, %3\n" /* Current logical CPU */

: "=r" (high1), "=r" (low1), "=r" (tscaux1), "=r" (cpuid1) :: "%rax", "%rbx", "%rcx", "%rdx" );

printf("Before code: IA32_TSC_AUX = %" PRIu32 "; CPU_ID = %" PRIu32 "\n", tscaux0, cpuid0); printf("After code: IA32_TSC_AUX = %" PRIu32 "; CPU_ID = %" PRIu32 "\n", tscaux1, cpuid1);

if (tscaux0 != tscaux1 || cpuid0 != cpuid1) fprintf(stderr, "Migration is occured\n"); }

$ ./testsBefore code: IA32_TSC_AUX = 0; CPU_ID = 0After code: IA32_TSC_AUX = 1; CPU_ID = 2Migration is occurred

Page 60: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Что осталось за кадром

■ Преобразование значений TSC в секунды (измерение TSC rate)

■ Синхронизация TSC в многопроцессорной SMP/NUMA-системе

■ Виртуализация TSC (KVM, QEMU, VirtualBox, ...)

■ Запись и возможность отключение TSC (флаг TSD)

■ Реализация измерений в пространстве ядра (прерывания, вытеснения, ….)

Page 61: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

Ссылки

■ Intel 64 and IA-32 Architectures Software Developer’s Manual //http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

■ AMD64 Architecture Programmer’s Manual //http://developer.amd.com/resources/documentation-articles/developer-guides-manuals/

■ How to Benchmark Code Execution Times on Intel IA-32 and IA-64 Instruction Set Architectures // Intel White Paper, 2010http://www.intel.ru/content/www/ru/ru/intelligent-systems/embedded-systems-training/ia-32-ia-64-benchmark-code-execution-paper.html

■ Using the RDTSC Instruction for Performance Monitoring // Intel, 1997https://www.ccsl.carleton.ca/~jamuir/rdtscpm1.pdf

■ Red Hat Enterprise MRG 2: Realtime Tuning Guide // https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_MRG/2/html/Realtime_Tuning_Guide/index.html

■ Agner Fog. Test programs for measuring clock cycles and performance monitoring // http://www.agner.org/optimize/#testp

■ Курносов М.Г. MPIPerf: пакет оценки эффективности коммуникационных функций стандарта MPI // ПаВТ-2012,http://www.mkurnosov.net/uploads/Main/kurnosov-pavt-2012.pdf

Page 62: Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64

$ git clone https://github.com/mkurnosov/tscbench.git

Исходный код TSCBench