SlideShare a Scribd company logo
Использование Time-Stamp Counter
для измерения времени выполнения кода
на процессорах с архитектурой Intel 64 и IA-32
Михаил Курносов
E-mail: mkurnosov@gmail.com
WWW: www.mkurnosov.net
Центр параллельных вычислительных технологий
Сибирский государственный университет телекоммуникаций и информатики
Новосибирск, 2014
Подсистема 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 Counter
http://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 Counter
http://developer.amd.com/resources/documentation-articles/developer-guides-manuals/
Подсистема Time-Stamp Counter (TSC)
■ Каждый логический процессор имеет свою подсистему TSC
(флаг TSC, флаг TSD, MSR IA32_TIME_STAMP_COUNTER)
■ В многопроцессорной системе (SMP/NUMA) значения TSC
логических процессоров могут отличаться
○ процессоры выполнили перезагрузку в разные
моменты времени
○ разная процедура инициализации процессоров
Счетчик 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
Алгоритмы увеличения 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
Алгоритмы увеличения 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)
[постоянная скорость] [останавливаемый]
Управление питанием и частотой
Двухпроцессорный сервер Intel
(системная плата Intel SR2520SAF)
frontend кластера Xeon32
Ноутбук Lenovo ThinkPad X230
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
[постоянная скорость] [неостанавливаемый]
Проверка доступности 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, %%eaxn"
"cpuidn"
"movl %%edx, %0n"
: "=r" (edx) /* Output */
: /* Input */
: "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */
);
return edx & (1U << 8) ? 1 : 0;
}
■ В ядре Linux TSC характеризуется двумя аттрибутами:
○ constant_tsc - счетчик TSC увеличивается с постоянной скоростью
○ nonstop_tsc - счетчик не останавливается во всех ACPI C-состояниях
■ Invariant TSC = constant_tsc + nonstop_tsc
$ cat /proc/cpuinfo | grep tsc
flags : 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
Поддержка 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();
}
...
}
Доступ к счетчику TSC
■ Чтение счетчика TSC
○ Инструкции RDTSC
○ Инструкция RDMSR (адрес IA32_TIME_STAMP_COUNTER: 0x10)
○ Инструкция RDTSCP
■ Запись в счетчик TSC
○ Инстуркция WRMSR (адрес 0x10)
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__ (
"rdtscn"
"movl %%edx, %0n"
"movl %%eax, %1n"
: "=r" (high), "=r" (low) /* Output */
: /* Input */
: "%rax", "%rbx",
"%rcx", "%rdx" /* Clobbered regs. */
);
uint64_t ticks = ((uint64_t)high << 32) | low;
Инструкция 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
Измерение времени выполнения кода
■ Имеется фрагмент кода (функция),
требуется измерить продолжительность его выполнения
■ Продолжительность выполнения кода (далее, время) может быть
выражена в секундах, тактах процессора/системной шины и пр.
t0 = get_time();
MEASURED_CODE();
t1 = get_time();
elapsed_time = t1 - t0;
■ Результаты измерений должны быть воспроизводимыми
(повторный запуск измерений должен давать такие же
результаты или близкие к ним)
■ Без воспроизводимости невозможно осуществлять
оптимизацию кода: как понять, что стало причиной
сокращения времени выполнения кода -
оптимизация или это ошибка измерений?
Методика измерения времени выполнения кода
1. Готовим систему к проведению измерений
○ настраиваем аппаратные подсистемы (настройки BIOS)
○ параметры операционной системы и процесса, в котором будут осуществляться измерения
2. Выполняем разогревочный вызов измеряемого кода (Warmup)
○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике
(при первом вызове может осуществляться отложенная инициализация и пр.)
3. Выполняем многократные запуски и собираем статистику о времени выполнения
○ Каждый запуск должен осуществляться в одних и тех же условиях
(входные массивы заполнены одними и теми же данными,
входные данные отсутствуют/присутствуют в кеш-памяти процессора, …)
○ Выполняем измерения пока:
✓ относительная стандартная ошибка среднего времени выполнения (RSE) больше 5%
[опционально]
✓ число выполненных измерений меньше максимально допустимого
Методика измерения времени выполнения кода
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)
■ Математическое ожидание времени выполнения (mean)
■ Несмещенная оценка дисперсии времени выполнения (unbiased sample variance - Var)
■ Стандартное отклонение (corrected sample standard deviation - StdDev)
■ Стандартная ошибка среднего времени выполнения (standard error of the mean - StdErr)
■ Относительная стандартная ошибка среднего времени выполнения
(relative standard error of the mean - RSE)
Элементарная обработка результатов измерений
Элементарная обработка результатов измерений
■ RSE показывает на сколько близко вычисленное среднее время выполнения
к истинному среднему времени выполнения (среднему генеральной совокупности)
○ На практике хорошая точность RSE <= 5%
■ Оценка погрешности при большом числе измерений
○ С вероятностью 0,997 время выполнения лежит в интервале
○ С вероятностью 0,955 время выполнения лежит в интервале
○ С вероятностью 0,683 время выполнения лежит в интервале
■ Оценка погрешности при малом числе измерений (n < 10)
○ Задаем требуемый уровень α доверительной вероятности (0.95; 0.99), из таблицы берем
значение коэффициента Стьюдента
■ Стандартный подход: s2
может быть < 0, ошибка при вычислении корня
■ Метод B.P. Welford’а
Knuth D. The Art of Computer Programming, Vol. 2, 3ed. (p. 232)
Вычисление стандартного отклонения (StdEv, StdDev)
Построение графиков и анализ результатов
Результат 100 запусков бенчмарка:
на каждом запуске многократно измерялось время выполнения кода и формировалась статистика
Каждая точка “+“
- это среднее время
выполнение функции
(результат запуска
бенчмарка)
Построение графиков и анализ результатов
Результат 100 запусков бенчмарка:
на каждом запуске многократно измерялось время выполнения кода и формировалась статистика
Каждая точка “+“
- это среднее время
выполнение функции
(результат запуска
бенчмарка)
Статистика
по средним k
запусков позволят
делать выводы
о воспроизводимости
результатов
■ На практике не удается добиться идеальной воспроизводимости результатов измерений
○ Требуется статистическая обработка результатов
■ Факторы влияющие на точность измерений времени выполнения кода
○ Во время выполнения кода может произойти прерывание и переключение
на его обработчик (таймер, сетевая активность) - недетерминированность
○ Переключение контекста и миграция потока на другой логический процессор
(второе чтение TSC с него, TSC процессоров могут быть не синхронизированы -
результат некорректный)
○ Измеряемый код использует кеш-память процессора (данных, инструкций) -
это ресурс, который разделяется с другими потоками; при каждом измерении,
с большой вероятностью, кеш-память будет находится в другом состоянии;
разное количество попаданий в кеш (cache hit) - недетерминированность
Воспроизводимость результатов измерений
прерывания,
сигналы
переключение
контекста,
миграция
кеш-память,
виртуальная
память,
конвейер
■ Факторы влияющие на точность измерения времени выполнения кода
○ При включенной технологии Intel Hyper-Threading инструкции измеряемого кода
и команды другого логического процессора разделяют один суперскалярный конвейер -
недетерминированность
○ Подсистемы управления питанием и частотой процессора могут изменить
скорость обновления TSC (Intel SpeedStep, Intel TurboBoost; AMD Cool'n'Quiet, AMD Turbo Core)
- недетерминированность
○ Функционирование подсистемы управления частотой процессора
(CPU Power Management), приводит к недетерминированному времени выполнения
измеряемого кода - частота процессора становится стохастической величиной -
недетерминированность
Воспроизводимость результатов измерений (2)
Подготовка системы к измерениям
■ 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
Подготовка системы к измерениям
■ 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())
Подготовка системы к измерениям
■ 3) Настраиваем подсистемы, которые используются измеряемым кодом
○ На NUMA-системах (AMD + Hyper-Transport, Intel QPI) настраиваем политику выделения
памяти (set_mempolicy(), numa_set_membind(); numactl):
✓ только с локального NUMA-узла
✓ c удаленного NUMA-узла
○ Запрещаем подкачку страниц (paging, swapping: mlockall()/munlockall())
○ Организуем разогрев/сброс кеш-памяти различных уровней перед измерениями
Выполнение измерений
■ 4) Инициализируем подсистему TSC и измеряем накладные расходы на доступ к TSC
■ 5) Выполняем разогревочный вызов измеряемого кода (Warmup)
○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике
(при первом вызове может осуществляться отложенная инициализация и пр.)
■ 6) Выполняем многократные запуски и собираем статистику о времени выполнения
○ Каждый запуск должен осуществляться в одних и тех же условиях:
✓ входные массивы заполнены одними и теми же данными
✓ входные данные отсутствуют/присутствуют в кеш-памяти процессора
✓ …
○ Выполняем измерения пока:
✓ относительная стандартная ошибка среднего времени выполнения (RSE) > 5% [опционально]
✓ число выполненных измерений меньше максимально допустимого
■ 7) Проводим статистическую обработку результатов измерений
Стандартная схема использования TSC
1 uint32_t high, low;
2 __asm__ __volatile__ (
3 "rdtscn"
4 "movl %%edx, %0n"
5 "movl %%eax, %1n"
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 "rdtscn"
13 "movl %%edx, %0n"
14 "movl %%eax, %1n"
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
Проблемы стандартной схемы
■ Внеочередное выполнение команд (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
■ Внеочередное выполнение команд (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__ (
"rdtscn"
"movl %%edx, %0n"
"movl %%eax, %1n"
: "=r" (high), "=r" (low)
:: "%rax", "%rbx", "%rcx", "%rdx"
);
uint64_t ticks = ((uint64_t)high << 32) | low;
/* Code after */
Сериализующие инструкции и барьеры памяти
■ Сериализующие инструкции (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”
Проблемы: внеочередное выполнение команд
■ Решение 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()
Проблемы: внеочередное выполнение команд
■ Решение 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()
Решения с одним барьером при чтении TSC
Barrier
cpuid {l,m}fence rdtscp
rdtsc
CODE
Barrier
cpuid {l,m}fence rdtscp
rdtsc
cpuid
rdtsc
code
cpuid
rdtsc
1) Инструкции измеряемого кода “не выскакивают” за пределы cpuid
(могут выскочить за первый RDTSC)
2) Перед вторым RDTSC могут быть выполнены следующие за ним инструкции
(но после cpuid)
rdtscp
code
rdtscp
1) Инструкции измеряемого кода могут быть выполнены до первого RDTSCP
2) Перед вторым RDTSCP могут быть выполнены следующие за ним
инструкции
1) Только инструкции LOAD измеряемого кода “не выскакивают” за пределы
lfence (могут выскочить за первый RDTSC), инструкции другого типа могут
быть выполнены до lfence
2) Перед вторым lfence могут быть выполнены следующие инструкции
отличные от команд LOAD; перед RDTSC могут быть выполнены следующие
за ним инструкции
Только инструкции LOAD/STORE измеряемого кода “не выскакивают”
за пределы mfence
На процессорах AMD mfence - инструкция сериализации (как CPUID)
lfence
rdtsc
code
lfence
rdtsc
mfence
rdtsc
code
mfence
rdtsc
Решения с двумя барьерами при чтении TSC
cpuid
rdtsc
cpuid
code
cpuid
rdtsc
cpuid
Инструкции измеряемого кода
“не выскакивают” за пределы
второго и третьего cpuid
lfence
rdtsc
mfence
code
mfence
rdtsc
mfence
Процессоры Intel: инструкции LOAD и STORE измеряемого кода
“не выскакивают” за пределы второго и третьего mfence
Процессоры AMD: инструкции измеряемого кода
“не выскакивают” за пределы второго и третьего mfence
rdtscp
mfence
code
rdtscp
mfence
Процессоры Intel: инструкции LOAD и STORE измеряемого кода
“не выскакивают” за пределы первого и второго mfence
Процессоры AMD: инструкции измеряемого кода
“не выскакивают” за пределы первого и второго mfence
mfence
rdtsc
cpuid
code
cpuid
rdtsc
mfence
Intel & AMD CPU’s
mfence - барьер памяти для LOAD и STORE операций,
после него выполняется чтение TSC
Инструкции измеряемого кода “не выскакивают”
за пределы cpuid
mfence
rdtsc
mfence
code
mfence
rdtsc
mfence
rdtscp
cpuid
code
rdtscp
cpuid
lfence
rdtsc
cpuid
code
lfence
rdtsc
cpuid
AMD CPU’s
rdtscp
mfence
code
rdtscp
mfence
Какое решение использовать?
cpuid
rdtsc
cpuid
code
cpuid
rdtsc
cpuid
cpuid
rdtsc
code
cpuid
rdtsc
cpuid
rdtsc
code
lfence
rdtsc
cpuid
■ mfence - разная реализация для Intel и AMD
■ rdtscp - только в относительно “свежих” процессорах
■ lfence + rdtsc - гарантия упорядоченности только для операций LOAD
■ cpuid - поддерживатся большинством процессоров (высокая латентность)
■ Остановимся на следующих подходах
cpuid
rdtsc
code
cpuid
rdtsc
mfence
Std_mfence
cpuid
rdtsc
code
rdtscp
cpuid
Std Intel_howto Four_cpuidStd_lfence
read_tsc_before()
read_tsc_after()
■ На реализацию барьеров (сериализацию) требуется время, которое вносит ошибку
в измерения
■ Перед измерениями необходимо оценить время, требуемое для организации барьеров,
и вычесть его из результирующего времени выполнения кода
Учет накладных расходов на сериализацию
/* 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;
Измерение времени доступа к 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) как минимальное значение
по результатам нескольких запусков
Измерение времени доступа к TSC
uint64_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 запусков (стабилизировалось)
Измерение времени доступа к 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);
...
}
Время доступа к 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 ~ 96
StdDev ~ 0.4
RSE < 0.04 %
mean + 3 * sigma
Mean ~ 98.1
StdDev ~ 0.26
RSE < 0.03 %
Время доступа к 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.6
StdDev ~ 0.5
RSE < 0.02 %
Время доступа к 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.7
StdDev ~ 6.82
RSE < 0.8 %
mean + 3 * sigma
Mean ~ 92.6
StdDev ~ 4.89
RSE < 0.5 %
Mean ~ 97.3
StdDev ~ 1.58
RSE < 0.2 %
Время доступа к 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.4
StdDev ~ 4.09
RSE < 0.4 %
Удовлетворительная
воспроизводимость
Mean ~ 111.6
StdDev ~ 8.3
RSE < 0.8 %
Mean ~ 117
StdDev ~ 2.2
RSE < 0.2 %
Время доступа к 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 ~ 116
StdDev ~ 0.0
RSE = 0 %
Mean ~ 116.7
StdDev ~ 1.01
RSE < 0.1 %
mean + 3 * sigma
Время доступа к 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)
Удовлетворительная
воспроизводимость
Время доступа к 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()):
cpuid
rdtsc
cpuid
code
cpuid
rdtsc
cpuid
cpuid
rdtsc
code
cpuid
rdtsc
cpuid
rdtsc
code
lfence
rdtsc
cpuid
cpuid
rdtsc
code
cpuid
rdtsc
mfence
Std_mfence
cpuid
rdtsc
code
rdtscp
cpuid
Std Intel_howto Four_cpuidStd_lfence
Воспроизводимость измерений (тест Primes)
Std
Std_mfence
Intel_howto Std_lfence
Four_cpuid
Mean ~ 12 972 457
StdDev ~ 3849
RSE < 0.003 %
Mean ~ 12 973 034
StdDev ~ 4242
RSE < 0.003 %
Mean ~ 12 971 891
StdDev ~ 3169
RSE < 0.002 %
Mean ~ 12 971 669
StdDev ~ 3760
RSE < 0.003 %
Mean ~ 12 930 873
StdDev ~ 4321
RSE < 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()
Воспроизводимость измерений (тест 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 %
■ Измерение времени доступа к TSC
○ Вычисляем время доступа к TSC (overhead) как минимальное значение по результатам
нескольких запусков - функция measure_tsc_overhead()
■ Метод чтения TSC
○ При измерении времени выполнения фрагмента кода с большим числом операций
выбор способа чтения TSC (Std, Intel_howto, Std_lfence, Std_mfence, Four_cpuid) существенно
на результатах не сказывается -- используем Std, Intel_howto, Std_lfence
○ При измерении времени выполнения небольших участков кода (десятки инструкций)
необходимо учитывать возможное внеочередное выполнение команд; желательно
провести чтение TSC разными способами и проверить воспроизводимость результатов
Выводы
Исходный код TSCBench
int main()
{
if (!is_tsc_available()) {
fprintf(stderr, "# Error: TSC is not supported by this processorn");
exit(1);
}
prepare_system_for_benchmarking();
run_benchmark();
return 0;
}
$ git clone https://github.com/mkurnosov/tscbench.git
Исходный код TSCBench
void 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 classn");
else
printf("# Scheduling policy is changed to RT class with max priorityn");
/* Disable paging to swap area */
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0)
fprintf(stderr, "# [Warning!] Error locking pagesn");
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 lockedn");
}
}
Исходный код TSCBench
void 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);
Исходный код 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.2fn",
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
Исходный код TSCBench
static 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, %%eaxn"
"cpuidn" /* Serialize execution */
"rdtscn" /* Read TSC */
"movl %%edx, %0n" /* - high 32 bits */
"movl %%eax, %1n" /* - low 32 bits */
: "=r" (high), "=r" (low) /* Output */
: /* Input */
: "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */
);
return ((uint64_t)high << 32) | low;
}
Исходный код TSCBench
static 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, %%eaxn"
"cpuidn" /* Serialize: wait for all prev. ops */
"rdtscn" /* Read TSC */
"movl %%edx, %0n" /* - high 32 bits */
"movl %%eax, %1n" /* - low 32 bits */
: "=r" (high), "=r" (low) /* Output */
: /* Input */
: "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */
);
return ((uint64_t)high << 32) | low;
}
Проверка миграции потока на другой процессор
void check_migration()
{
register uint32_t high0, low0, high1, low1, tscaux0, tscaux1;
__asm__ __volatile__ (
"xorl %%eax, %%eaxn"
"cpuidn"
"rdtscpn" /* Загружаем IA32_TIME_STAMP_COUNTER и IA32_TSC_AUX в регистры */
"movl %%edx, %0n"
"movl %%eax, %1n"
"movl %%ecx, %2n" /* IA32_TSC_AUX MSR */
: "=r" (high0), "=r" (low0), "=r" (tscaux0)
:: "%rax", "%rbx", "%rcx", "%rdx"
);
/* Measured code */
__asm__ __volatile__ (
"rdtscpn"
"movl %%edx, %0n"
"movl %%eax, %1n"
"movl %%ecx, %2n"
"xorl %%eax, %%eaxn"
"cpuidn"
: "=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 CPUn");
}
$ ./tests
First RDTSCP: IA32_TSC_AUX = 0
Second RDTSCP: IA32_TSC_AUX = 0
Проверка миграции потока на другой процессор (2)
void check_migration_cpuid()
{
register uint32_t high0, low0, high1, low1,
tscaux0, tscaux1,
cpuid0, cpuid1;
__asm__ __volatile__ (
"movl $0x0b, %%eaxn" /* EAX=0x0b - information about processor */
"cpuidn"
"movl %%edx, %3n" /* x2APIC ID the current logical processor */
"rdtscpn"
"movl %%edx, %0n"
"movl %%eax, %1n"
"movl %%ecx, %2n" /* 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 affinityn");
__asm__ __volatile__ (
"rdtscpn"
"movl %%edx, %0n"
"movl %%eax, %1n"
"movl %%ecx, %2n" /* IA32_TSC_AUX MSR */
"movl $0x0b, %%eaxn"
"cpuidn"
"movl %%edx, %3n" /* 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 occuredn");
}
$ ./tests
Before code: IA32_TSC_AUX = 0; CPU_ID = 0
After code: IA32_TSC_AUX = 1; CPU_ID = 2
Migration is occurred
Что осталось за кадром
■ Преобразование значений TSC в секунды (измерение TSC rate)
■ Синхронизация TSC в многопроцессорной SMP/NUMA-системе
■ Виртуализация TSC (KVM, QEMU, VirtualBox, ...)
■ Запись и возможность отключение TSC (флаг TSD)
■ Реализация измерений в пространстве ядра (прерывания, вытеснения, ….)
Ссылки
■ 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, 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
■ 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
$ git clone https://github.com/mkurnosov/tscbench.git
Исходный код TSCBench

More Related Content

PDF
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
Mikhail Kurnosov
 
PDF
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Mikhail Kurnosov
 
PDF
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, paral...
Mikhail Kurnosov
 
PDF
Лекция 3: Векторизация кода (Code vectorization, SIMD, SSE, AVX)
Mikhail Kurnosov
 
PDF
Лекция 9. Программирование GPU
Mikhail Kurnosov
 
PDF
Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)
Mikhail Kurnosov
 
PDF
Лекция 3. Векторизация кода (Code vectorization: SSE, AVX)
Mikhail Kurnosov
 
PDF
Лекция 6. Стандарт OpenMP
Mikhail Kurnosov
 
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
Mikhail Kurnosov
 
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Mikhail Kurnosov
 
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, paral...
Mikhail Kurnosov
 
Лекция 3: Векторизация кода (Code vectorization, SIMD, SSE, AVX)
Mikhail Kurnosov
 
Лекция 9. Программирование GPU
Mikhail Kurnosov
 
Лекция 4. Векторизация кода (Code vectorization: SSE, AVX)
Mikhail Kurnosov
 
Лекция 3. Векторизация кода (Code vectorization: SSE, AVX)
Mikhail Kurnosov
 
Лекция 6. Стандарт OpenMP
Mikhail Kurnosov
 

What's hot (20)

PDF
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
Mikhail Kurnosov
 
PDF
Семинар 12. Параллельное программирование на MPI (часть 5)
Mikhail Kurnosov
 
PDF
Лекция 7. Язык параллельного программирования Intel Cilk Plus
Mikhail Kurnosov
 
PDF
Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...
Mikhail Kurnosov
 
PDF
Семинар 8. Параллельное программирование на MPI (часть 1)
Mikhail Kurnosov
 
PDF
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Mikhail Kurnosov
 
PDF
Лекция 2: Оптимизация ветвлений и циклов (Branch prediction and loops optimiz...
Mikhail Kurnosov
 
PDF
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Mikhail Kurnosov
 
PDF
Семинар 11. Параллельное программирование на MPI (часть 4)
Mikhail Kurnosov
 
PDF
Векторизация кода (семинар 1)
Mikhail Kurnosov
 
PDF
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Mikhail Kurnosov
 
PDF
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
Alexey Paznikov
 
PDF
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
Alexey Paznikov
 
PDF
Лекция 11: Программирование графических процессоров на NVIDIA CUDA
Mikhail Kurnosov
 
PDF
11 встреча — Введение в GPGPU (А. Свириденков)
Smolensk Computer Science Club
 
PDF
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Alexey Paznikov
 
PDF
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
Alexey Paznikov
 
PPT
Лекция №3 Организация ЭВМ и систем
pianist2317
 
PPTX
Ликбез по Эльбрусу, Константин Трушкин (МЦСТ)
Ontico
 
PDF
Лекция 8. Intel Threading Building Blocks
Mikhail Kurnosov
 
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
Mikhail Kurnosov
 
Семинар 12. Параллельное программирование на MPI (часть 5)
Mikhail Kurnosov
 
Лекция 7. Язык параллельного программирования Intel Cilk Plus
Mikhail Kurnosov
 
Лекция 3. Оптимизация доступа к памяти (Memory access optimization, cache opt...
Mikhail Kurnosov
 
Семинар 8. Параллельное программирование на MPI (часть 1)
Mikhail Kurnosov
 
Лекция 2. Оптимизация ветвлений и циклов (Branch prediction and loop optimiz...
Mikhail Kurnosov
 
Лекция 2: Оптимизация ветвлений и циклов (Branch prediction and loops optimiz...
Mikhail Kurnosov
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Mikhail Kurnosov
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Mikhail Kurnosov
 
Векторизация кода (семинар 1)
Mikhail Kurnosov
 
Семинар 5. Многопоточное программирование на OpenMP (часть 5)
Mikhail Kurnosov
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
Alexey Paznikov
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
Alexey Paznikov
 
Лекция 11: Программирование графических процессоров на NVIDIA CUDA
Mikhail Kurnosov
 
11 встреча — Введение в GPGPU (А. Свириденков)
Smolensk Computer Science Club
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
Alexey Paznikov
 
Лекция №3 Организация ЭВМ и систем
pianist2317
 
Ликбез по Эльбрусу, Константин Трушкин (МЦСТ)
Ontico
 
Лекция 8. Intel Threading Building Blocks
Mikhail Kurnosov
 
Ad

Viewers also liked (7)

PDF
Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)
Mikhail Kurnosov
 
PDF
Лекция 7. Стандарт OpenMP (подолжение)
Mikhail Kurnosov
 
PDF
Векторизация кода (семинар 3)
Mikhail Kurnosov
 
PDF
Векторизация кода (семинар 2)
Mikhail Kurnosov
 
PDF
Лекция 12 (часть 2): Языки программирования семейства PGAS: IBM X10
Mikhail Kurnosov
 
PDF
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Mikhail Kurnosov
 
PDF
Лекция 12: Трудноразрешимые задачи
Mikhail Kurnosov
 
Лекция 7. Декартовы деревья (Treaps, дучи, дерамиды)
Mikhail Kurnosov
 
Лекция 7. Стандарт OpenMP (подолжение)
Mikhail Kurnosov
 
Векторизация кода (семинар 3)
Mikhail Kurnosov
 
Векторизация кода (семинар 2)
Mikhail Kurnosov
 
Лекция 12 (часть 2): Языки программирования семейства PGAS: IBM X10
Mikhail Kurnosov
 
Лекция 12 (часть 1): Языки программирования семейства PGAS: Cray Chapel
Mikhail Kurnosov
 
Лекция 12: Трудноразрешимые задачи
Mikhail Kurnosov
 
Ad

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

PPTX
Обзор современных микроконтроллеров и их архитектур
IoT Community
 
PPT
TMPA-2013 Smirnov
Iosif Itkin
 
PPT
prezlec_Історія.ppt
ssusere2bc36
 
PPTX
Аппаратная реализация персонального компьютера
student_SSGA
 
PPT
Prez osob mikroproc
semsemsemsemsem
 
PDF
Лекция 1: Архитектурно-ориентированная оптимизация программного обеспечения (...
Mikhail Kurnosov
 
DOC
023
JIuc
 
PPT
Процессоры и составляющие системного блока
Wewillneversleep
 
PPT
Лекция № 3 Организация ЭВМ и систем
Александр Силантьев
 
PDF
Обработка данных с датчиков Холла
ishevchuk
 
PPTX
ADD 2011: Статический анализ Си++ кода
Andrey Karpov
 
PPTX
Статический анализ Си++ кода
Tatyanazaxarova
 
PPTX
[DD] 12. Arithmetic logic device
Gabit Altybaev
 
PDF
Использование C++ для низкоуровневой платформозависимой разработки — Кирилл ...
Yandex
 
PPT
Лекция № 2 Организация ЭВМ и систем
Александр Силантьев
 
PPT
Лекция №2 Организация ЭВМ и систем
pianist2317
 
PDF
Архитектура и программирование потоковых многоядерных процессоров для научных...
a15464321646213
 
PPTX
новый взгляд на Mes системы с точки зрения теории ограничений ефремов-siams
Expolink
 
PPTX
Доклад в Mail.ru 01.11.12
Alex Tutubalin
 
Обзор современных микроконтроллеров и их архитектур
IoT Community
 
TMPA-2013 Smirnov
Iosif Itkin
 
prezlec_Історія.ppt
ssusere2bc36
 
Аппаратная реализация персонального компьютера
student_SSGA
 
Prez osob mikroproc
semsemsemsemsem
 
Лекция 1: Архитектурно-ориентированная оптимизация программного обеспечения (...
Mikhail Kurnosov
 
023
JIuc
 
Процессоры и составляющие системного блока
Wewillneversleep
 
Лекция № 3 Организация ЭВМ и систем
Александр Силантьев
 
Обработка данных с датчиков Холла
ishevchuk
 
ADD 2011: Статический анализ Си++ кода
Andrey Karpov
 
Статический анализ Си++ кода
Tatyanazaxarova
 
[DD] 12. Arithmetic logic device
Gabit Altybaev
 
Использование C++ для низкоуровневой платформозависимой разработки — Кирилл ...
Yandex
 
Лекция № 2 Организация ЭВМ и систем
Александр Силантьев
 
Лекция №2 Организация ЭВМ и систем
pianist2317
 
Архитектура и программирование потоковых многоядерных процессоров для научных...
a15464321646213
 
новый взгляд на Mes системы с точки зрения теории ограничений ефремов-siams
Expolink
 
Доклад в Mail.ru 01.11.12
Alex Tutubalin
 

More from Mikhail Kurnosov (16)

PDF
Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)
Mikhail Kurnosov
 
PDF
Лекция 5. B-деревья (B-trees, k-way merge sort)
Mikhail Kurnosov
 
PDF
Лекция 4. Префиксные деревья (tries, prefix trees)
Mikhail Kurnosov
 
PDF
Лекция 3. АВЛ-деревья (AVL trees)
Mikhail Kurnosov
 
PDF
Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...
Mikhail Kurnosov
 
PDF
Лекция 1. Амортизационный анализ (amortized analysis)
Mikhail Kurnosov
 
PDF
Лекция 11. Методы разработки алгоритмов
Mikhail Kurnosov
 
PDF
Лекция 10. Графы. Остовные деревья минимальной стоимости
Mikhail Kurnosov
 
PDF
Семинар 10. Параллельное программирование на MPI (часть 3)
Mikhail Kurnosov
 
PDF
Семинар 9. Параллельное программирование на MPI (часть 2)
Mikhail Kurnosov
 
PDF
Лекция 9. Поиск кратчайшего пути в графе
Mikhail Kurnosov
 
PDF
Лекция 8. Графы. Обходы графов
Mikhail Kurnosov
 
PDF
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Mikhail Kurnosov
 
PDF
Лекция 7. Бинарные кучи. Пирамидальная сортировка
Mikhail Kurnosov
 
PDF
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Mikhail Kurnosov
 
PDF
Лекция 6. Хеш-таблицы
Mikhail Kurnosov
 
Лекция 6. Фибоначчиевы кучи (Fibonacci heaps)
Mikhail Kurnosov
 
Лекция 5. B-деревья (B-trees, k-way merge sort)
Mikhail Kurnosov
 
Лекция 4. Префиксные деревья (tries, prefix trees)
Mikhail Kurnosov
 
Лекция 3. АВЛ-деревья (AVL trees)
Mikhail Kurnosov
 
Лекция 2. Красно-чёрные деревья (Red-black trees). Скошенные деревья (Splay t...
Mikhail Kurnosov
 
Лекция 1. Амортизационный анализ (amortized analysis)
Mikhail Kurnosov
 
Лекция 11. Методы разработки алгоритмов
Mikhail Kurnosov
 
Лекция 10. Графы. Остовные деревья минимальной стоимости
Mikhail Kurnosov
 
Семинар 10. Параллельное программирование на MPI (часть 3)
Mikhail Kurnosov
 
Семинар 9. Параллельное программирование на MPI (часть 2)
Mikhail Kurnosov
 
Лекция 9. Поиск кратчайшего пути в графе
Mikhail Kurnosov
 
Лекция 8. Графы. Обходы графов
Mikhail Kurnosov
 
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Mikhail Kurnosov
 
Лекция 7. Бинарные кучи. Пирамидальная сортировка
Mikhail Kurnosov
 
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Mikhail Kurnosov
 
Лекция 6. Хеш-таблицы
Mikhail Kurnosov
 

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

  • 1. Использование Time-Stamp Counter для измерения времени выполнения кода на процессорах с архитектурой Intel 64 и IA-32 Михаил Курносов E-mail: [email protected] WWW: www.mkurnosov.net Центр параллельных вычислительных технологий Сибирский государственный университет телекоммуникаций и информатики Новосибирск, 2014
  • 2. Подсистема 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 Counter http://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 Counter http://developer.amd.com/resources/documentation-articles/developer-guides-manuals/
  • 3. Подсистема Time-Stamp Counter (TSC) ■ Каждый логический процессор имеет свою подсистему TSC (флаг TSC, флаг TSD, MSR IA32_TIME_STAMP_COUNTER) ■ В многопроцессорной системе (SMP/NUMA) значения TSC логических процессоров могут отличаться ○ процессоры выполнили перезагрузку в разные моменты времени ○ разная процедура инициализации процессоров
  • 4. Счетчик 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
  • 5. Алгоритмы увеличения 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
  • 6. Алгоритмы увеличения 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) [постоянная скорость] [останавливаемый]
  • 7. Управление питанием и частотой Двухпроцессорный сервер Intel (системная плата Intel SR2520SAF) frontend кластера Xeon32 Ноутбук Lenovo ThinkPad X230
  • 8. 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 [постоянная скорость] [неостанавливаемый]
  • 9. Проверка доступности 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, %%eaxn" "cpuidn" "movl %%edx, %0n" : "=r" (edx) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ ); return edx & (1U << 8) ? 1 : 0; }
  • 10. ■ В ядре Linux TSC характеризуется двумя аттрибутами: ○ constant_tsc - счетчик TSC увеличивается с постоянной скоростью ○ nonstop_tsc - счетчик не останавливается во всех ACPI C-состояниях ■ Invariant TSC = constant_tsc + nonstop_tsc $ cat /proc/cpuinfo | grep tsc flags : 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
  • 11. Поддержка 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(); } ... }
  • 12. Доступ к счетчику TSC ■ Чтение счетчика TSC ○ Инструкции RDTSC ○ Инструкция RDMSR (адрес IA32_TIME_STAMP_COUNTER: 0x10) ○ Инструкция RDTSCP ■ Запись в счетчик TSC ○ Инстуркция WRMSR (адрес 0x10)
  • 13. 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__ ( "rdtscn" "movl %%edx, %0n" "movl %%eax, %1n" : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered regs. */ ); uint64_t ticks = ((uint64_t)high << 32) | low;
  • 14. Инструкция 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
  • 15. Измерение времени выполнения кода ■ Имеется фрагмент кода (функция), требуется измерить продолжительность его выполнения ■ Продолжительность выполнения кода (далее, время) может быть выражена в секундах, тактах процессора/системной шины и пр. t0 = get_time(); MEASURED_CODE(); t1 = get_time(); elapsed_time = t1 - t0; ■ Результаты измерений должны быть воспроизводимыми (повторный запуск измерений должен давать такие же результаты или близкие к ним) ■ Без воспроизводимости невозможно осуществлять оптимизацию кода: как понять, что стало причиной сокращения времени выполнения кода - оптимизация или это ошибка измерений?
  • 16. Методика измерения времени выполнения кода 1. Готовим систему к проведению измерений ○ настраиваем аппаратные подсистемы (настройки BIOS) ○ параметры операционной системы и процесса, в котором будут осуществляться измерения 2. Выполняем разогревочный вызов измеряемого кода (Warmup) ○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике (при первом вызове может осуществляться отложенная инициализация и пр.) 3. Выполняем многократные запуски и собираем статистику о времени выполнения ○ Каждый запуск должен осуществляться в одних и тех же условиях (входные массивы заполнены одними и теми же данными, входные данные отсутствуют/присутствуют в кеш-памяти процессора, …) ○ Выполняем измерения пока: ✓ относительная стандартная ошибка среднего времени выполнения (RSE) больше 5% [опционально] ✓ число выполненных измерений меньше максимально допустимого
  • 17. Методика измерения времени выполнения кода 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)
  • 18. ■ Математическое ожидание времени выполнения (mean) ■ Несмещенная оценка дисперсии времени выполнения (unbiased sample variance - Var) ■ Стандартное отклонение (corrected sample standard deviation - StdDev) ■ Стандартная ошибка среднего времени выполнения (standard error of the mean - StdErr) ■ Относительная стандартная ошибка среднего времени выполнения (relative standard error of the mean - RSE) Элементарная обработка результатов измерений
  • 19. Элементарная обработка результатов измерений ■ RSE показывает на сколько близко вычисленное среднее время выполнения к истинному среднему времени выполнения (среднему генеральной совокупности) ○ На практике хорошая точность RSE <= 5% ■ Оценка погрешности при большом числе измерений ○ С вероятностью 0,997 время выполнения лежит в интервале ○ С вероятностью 0,955 время выполнения лежит в интервале ○ С вероятностью 0,683 время выполнения лежит в интервале ■ Оценка погрешности при малом числе измерений (n < 10) ○ Задаем требуемый уровень α доверительной вероятности (0.95; 0.99), из таблицы берем значение коэффициента Стьюдента
  • 20. ■ Стандартный подход: s2 может быть < 0, ошибка при вычислении корня ■ Метод B.P. Welford’а Knuth D. The Art of Computer Programming, Vol. 2, 3ed. (p. 232) Вычисление стандартного отклонения (StdEv, StdDev)
  • 21. Построение графиков и анализ результатов Результат 100 запусков бенчмарка: на каждом запуске многократно измерялось время выполнения кода и формировалась статистика Каждая точка “+“ - это среднее время выполнение функции (результат запуска бенчмарка)
  • 22. Построение графиков и анализ результатов Результат 100 запусков бенчмарка: на каждом запуске многократно измерялось время выполнения кода и формировалась статистика Каждая точка “+“ - это среднее время выполнение функции (результат запуска бенчмарка) Статистика по средним k запусков позволят делать выводы о воспроизводимости результатов
  • 23. ■ На практике не удается добиться идеальной воспроизводимости результатов измерений ○ Требуется статистическая обработка результатов ■ Факторы влияющие на точность измерений времени выполнения кода ○ Во время выполнения кода может произойти прерывание и переключение на его обработчик (таймер, сетевая активность) - недетерминированность ○ Переключение контекста и миграция потока на другой логический процессор (второе чтение TSC с него, TSC процессоров могут быть не синхронизированы - результат некорректный) ○ Измеряемый код использует кеш-память процессора (данных, инструкций) - это ресурс, который разделяется с другими потоками; при каждом измерении, с большой вероятностью, кеш-память будет находится в другом состоянии; разное количество попаданий в кеш (cache hit) - недетерминированность Воспроизводимость результатов измерений прерывания, сигналы переключение контекста, миграция кеш-память, виртуальная память, конвейер
  • 24. ■ Факторы влияющие на точность измерения времени выполнения кода ○ При включенной технологии Intel Hyper-Threading инструкции измеряемого кода и команды другого логического процессора разделяют один суперскалярный конвейер - недетерминированность ○ Подсистемы управления питанием и частотой процессора могут изменить скорость обновления TSC (Intel SpeedStep, Intel TurboBoost; AMD Cool'n'Quiet, AMD Turbo Core) - недетерминированность ○ Функционирование подсистемы управления частотой процессора (CPU Power Management), приводит к недетерминированному времени выполнения измеряемого кода - частота процессора становится стохастической величиной - недетерминированность Воспроизводимость результатов измерений (2)
  • 25. Подготовка системы к измерениям ■ 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
  • 26. Подготовка системы к измерениям ■ 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())
  • 27. Подготовка системы к измерениям ■ 3) Настраиваем подсистемы, которые используются измеряемым кодом ○ На NUMA-системах (AMD + Hyper-Transport, Intel QPI) настраиваем политику выделения памяти (set_mempolicy(), numa_set_membind(); numactl): ✓ только с локального NUMA-узла ✓ c удаленного NUMA-узла ○ Запрещаем подкачку страниц (paging, swapping: mlockall()/munlockall()) ○ Организуем разогрев/сброс кеш-памяти различных уровней перед измерениями
  • 28. Выполнение измерений ■ 4) Инициализируем подсистему TSC и измеряем накладные расходы на доступ к TSC ■ 5) Выполняем разогревочный вызов измеряемого кода (Warmup) ○ Регистрируем время выполнения первого вызова и не учитываем его в общей статистике (при первом вызове может осуществляться отложенная инициализация и пр.) ■ 6) Выполняем многократные запуски и собираем статистику о времени выполнения ○ Каждый запуск должен осуществляться в одних и тех же условиях: ✓ входные массивы заполнены одними и теми же данными ✓ входные данные отсутствуют/присутствуют в кеш-памяти процессора ✓ … ○ Выполняем измерения пока: ✓ относительная стандартная ошибка среднего времени выполнения (RSE) > 5% [опционально] ✓ число выполненных измерений меньше максимально допустимого ■ 7) Проводим статистическую обработку результатов измерений
  • 29. Стандартная схема использования TSC 1 uint32_t high, low; 2 __asm__ __volatile__ ( 3 "rdtscn" 4 "movl %%edx, %0n" 5 "movl %%eax, %1n" 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 "rdtscn" 13 "movl %%edx, %0n" 14 "movl %%eax, %1n" 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
  • 30. Проблемы стандартной схемы ■ Внеочередное выполнение команд (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
  • 31. ■ Внеочередное выполнение команд (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__ ( "rdtscn" "movl %%edx, %0n" "movl %%eax, %1n" : "=r" (high), "=r" (low) :: "%rax", "%rbx", "%rcx", "%rdx" ); uint64_t ticks = ((uint64_t)high << 32) | low; /* Code after */
  • 32. Сериализующие инструкции и барьеры памяти ■ Сериализующие инструкции (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”
  • 33. Проблемы: внеочередное выполнение команд ■ Решение 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()
  • 34. Проблемы: внеочередное выполнение команд ■ Решение 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()
  • 35. Решения с одним барьером при чтении TSC Barrier cpuid {l,m}fence rdtscp rdtsc CODE Barrier cpuid {l,m}fence rdtscp rdtsc cpuid rdtsc code cpuid rdtsc 1) Инструкции измеряемого кода “не выскакивают” за пределы cpuid (могут выскочить за первый RDTSC) 2) Перед вторым RDTSC могут быть выполнены следующие за ним инструкции (но после cpuid) rdtscp code rdtscp 1) Инструкции измеряемого кода могут быть выполнены до первого RDTSCP 2) Перед вторым RDTSCP могут быть выполнены следующие за ним инструкции 1) Только инструкции LOAD измеряемого кода “не выскакивают” за пределы lfence (могут выскочить за первый RDTSC), инструкции другого типа могут быть выполнены до lfence 2) Перед вторым lfence могут быть выполнены следующие инструкции отличные от команд LOAD; перед RDTSC могут быть выполнены следующие за ним инструкции Только инструкции LOAD/STORE измеряемого кода “не выскакивают” за пределы mfence На процессорах AMD mfence - инструкция сериализации (как CPUID) lfence rdtsc code lfence rdtsc mfence rdtsc code mfence rdtsc
  • 36. Решения с двумя барьерами при чтении TSC cpuid rdtsc cpuid code cpuid rdtsc cpuid Инструкции измеряемого кода “не выскакивают” за пределы второго и третьего cpuid lfence rdtsc mfence code mfence rdtsc mfence Процессоры Intel: инструкции LOAD и STORE измеряемого кода “не выскакивают” за пределы второго и третьего mfence Процессоры AMD: инструкции измеряемого кода “не выскакивают” за пределы второго и третьего mfence rdtscp mfence code rdtscp mfence Процессоры Intel: инструкции LOAD и STORE измеряемого кода “не выскакивают” за пределы первого и второго mfence Процессоры AMD: инструкции измеряемого кода “не выскакивают” за пределы первого и второго mfence mfence rdtsc cpuid code cpuid rdtsc mfence Intel & AMD CPU’s mfence - барьер памяти для LOAD и STORE операций, после него выполняется чтение TSC Инструкции измеряемого кода “не выскакивают” за пределы cpuid mfence rdtsc mfence code mfence rdtsc mfence rdtscp cpuid code rdtscp cpuid lfence rdtsc cpuid code lfence rdtsc cpuid AMD CPU’s rdtscp mfence code rdtscp mfence
  • 37. Какое решение использовать? cpuid rdtsc cpuid code cpuid rdtsc cpuid cpuid rdtsc code cpuid rdtsc cpuid rdtsc code lfence rdtsc cpuid ■ mfence - разная реализация для Intel и AMD ■ rdtscp - только в относительно “свежих” процессорах ■ lfence + rdtsc - гарантия упорядоченности только для операций LOAD ■ cpuid - поддерживатся большинством процессоров (высокая латентность) ■ Остановимся на следующих подходах cpuid rdtsc code cpuid rdtsc mfence Std_mfence cpuid rdtsc code rdtscp cpuid Std Intel_howto Four_cpuidStd_lfence read_tsc_before() read_tsc_after()
  • 38. ■ На реализацию барьеров (сериализацию) требуется время, которое вносит ошибку в измерения ■ Перед измерениями необходимо оценить время, требуемое для организации барьеров, и вычесть его из результирующего времени выполнения кода Учет накладных расходов на сериализацию /* 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;
  • 39. Измерение времени доступа к 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) как минимальное значение по результатам нескольких запусков
  • 40. Измерение времени доступа к TSC uint64_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 запусков (стабилизировалось)
  • 41. Измерение времени доступа к 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); ... }
  • 42. Время доступа к 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 ~ 96 StdDev ~ 0.4 RSE < 0.04 % mean + 3 * sigma Mean ~ 98.1 StdDev ~ 0.26 RSE < 0.03 %
  • 43. Время доступа к 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.6 StdDev ~ 0.5 RSE < 0.02 %
  • 44. Время доступа к 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.7 StdDev ~ 6.82 RSE < 0.8 % mean + 3 * sigma Mean ~ 92.6 StdDev ~ 4.89 RSE < 0.5 % Mean ~ 97.3 StdDev ~ 1.58 RSE < 0.2 %
  • 45. Время доступа к 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.4 StdDev ~ 4.09 RSE < 0.4 % Удовлетворительная воспроизводимость Mean ~ 111.6 StdDev ~ 8.3 RSE < 0.8 % Mean ~ 117 StdDev ~ 2.2 RSE < 0.2 %
  • 46. Время доступа к 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 ~ 116 StdDev ~ 0.0 RSE = 0 % Mean ~ 116.7 StdDev ~ 1.01 RSE < 0.1 % mean + 3 * sigma
  • 47. Время доступа к 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) Удовлетворительная воспроизводимость
  • 48. Время доступа к 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()): cpuid rdtsc cpuid code cpuid rdtsc cpuid cpuid rdtsc code cpuid rdtsc cpuid rdtsc code lfence rdtsc cpuid cpuid rdtsc code cpuid rdtsc mfence Std_mfence cpuid rdtsc code rdtscp cpuid Std Intel_howto Four_cpuidStd_lfence
  • 49. Воспроизводимость измерений (тест Primes) Std Std_mfence Intel_howto Std_lfence Four_cpuid Mean ~ 12 972 457 StdDev ~ 3849 RSE < 0.003 % Mean ~ 12 973 034 StdDev ~ 4242 RSE < 0.003 % Mean ~ 12 971 891 StdDev ~ 3169 RSE < 0.002 % Mean ~ 12 971 669 StdDev ~ 3760 RSE < 0.003 % Mean ~ 12 930 873 StdDev ~ 4321 RSE < 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()
  • 50. Воспроизводимость измерений (тест 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 %
  • 51. ■ Измерение времени доступа к TSC ○ Вычисляем время доступа к TSC (overhead) как минимальное значение по результатам нескольких запусков - функция measure_tsc_overhead() ■ Метод чтения TSC ○ При измерении времени выполнения фрагмента кода с большим числом операций выбор способа чтения TSC (Std, Intel_howto, Std_lfence, Std_mfence, Four_cpuid) существенно на результатах не сказывается -- используем Std, Intel_howto, Std_lfence ○ При измерении времени выполнения небольших участков кода (десятки инструкций) необходимо учитывать возможное внеочередное выполнение команд; желательно провести чтение TSC разными способами и проверить воспроизводимость результатов Выводы
  • 52. Исходный код TSCBench int main() { if (!is_tsc_available()) { fprintf(stderr, "# Error: TSC is not supported by this processorn"); exit(1); } prepare_system_for_benchmarking(); run_benchmark(); return 0; } $ git clone https://github.com/mkurnosov/tscbench.git
  • 53. Исходный код TSCBench void 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 classn"); else printf("# Scheduling policy is changed to RT class with max priorityn"); /* Disable paging to swap area */ if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) fprintf(stderr, "# [Warning!] Error locking pagesn"); 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 lockedn"); } }
  • 54. Исходный код TSCBench void 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);
  • 55. Исходный код 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.2fn", 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
  • 56. Исходный код TSCBench static 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, %%eaxn" "cpuidn" /* Serialize execution */ "rdtscn" /* Read TSC */ "movl %%edx, %0n" /* - high 32 bits */ "movl %%eax, %1n" /* - low 32 bits */ : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ ); return ((uint64_t)high << 32) | low; }
  • 57. Исходный код TSCBench static 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, %%eaxn" "cpuidn" /* Serialize: wait for all prev. ops */ "rdtscn" /* Read TSC */ "movl %%edx, %0n" /* - high 32 bits */ "movl %%eax, %1n" /* - low 32 bits */ : "=r" (high), "=r" (low) /* Output */ : /* Input */ : "%rax", "%rbx", "%rcx", "%rdx" /* Clobbered registers */ ); return ((uint64_t)high << 32) | low; }
  • 58. Проверка миграции потока на другой процессор void check_migration() { register uint32_t high0, low0, high1, low1, tscaux0, tscaux1; __asm__ __volatile__ ( "xorl %%eax, %%eaxn" "cpuidn" "rdtscpn" /* Загружаем IA32_TIME_STAMP_COUNTER и IA32_TSC_AUX в регистры */ "movl %%edx, %0n" "movl %%eax, %1n" "movl %%ecx, %2n" /* IA32_TSC_AUX MSR */ : "=r" (high0), "=r" (low0), "=r" (tscaux0) :: "%rax", "%rbx", "%rcx", "%rdx" ); /* Measured code */ __asm__ __volatile__ ( "rdtscpn" "movl %%edx, %0n" "movl %%eax, %1n" "movl %%ecx, %2n" "xorl %%eax, %%eaxn" "cpuidn" : "=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 CPUn"); } $ ./tests First RDTSCP: IA32_TSC_AUX = 0 Second RDTSCP: IA32_TSC_AUX = 0
  • 59. Проверка миграции потока на другой процессор (2) void check_migration_cpuid() { register uint32_t high0, low0, high1, low1, tscaux0, tscaux1, cpuid0, cpuid1; __asm__ __volatile__ ( "movl $0x0b, %%eaxn" /* EAX=0x0b - information about processor */ "cpuidn" "movl %%edx, %3n" /* x2APIC ID the current logical processor */ "rdtscpn" "movl %%edx, %0n" "movl %%eax, %1n" "movl %%ecx, %2n" /* 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 affinityn"); __asm__ __volatile__ ( "rdtscpn" "movl %%edx, %0n" "movl %%eax, %1n" "movl %%ecx, %2n" /* IA32_TSC_AUX MSR */ "movl $0x0b, %%eaxn" "cpuidn" "movl %%edx, %3n" /* 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 occuredn"); } $ ./tests Before code: IA32_TSC_AUX = 0; CPU_ID = 0 After code: IA32_TSC_AUX = 1; CPU_ID = 2 Migration is occurred
  • 60. Что осталось за кадром ■ Преобразование значений TSC в секунды (измерение TSC rate) ■ Синхронизация TSC в многопроцессорной SMP/NUMA-системе ■ Виртуализация TSC (KVM, QEMU, VirtualBox, ...) ■ Запись и возможность отключение TSC (флаг TSD) ■ Реализация измерений в пространстве ядра (прерывания, вытеснения, ….)
  • 61. Ссылки ■ 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, 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 ■ 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
  • 62. $ git clone https://github.com/mkurnosov/tscbench.git Исходный код TSCBench