Документация Engee

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

Страница в процессе перевода.

Введение

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

Когда я начал работать над регрессионным тестированием производительности кода на языке Julia, я был удивлен тем, что не смог найти актуальный и понятный новичкам контрольный список, в котором бы обобщались знания, разбросанные по различным форумам и статьям. Надеюсь, что этот документ сможет послужить отправной точкой для тех, кто только начинает заниматься тестированием производительности в Linux и пытается понять, почему, казалось бы, идентичные испытания производительности дают существенно различающиеся результаты.

Непосвященным поиск и устранение искажений из-за ОС порой может казаться скорее искусством, чем наукой. Очень быстро вы столкнетесь с тем, что для создания надлежащей среды для тщательного тестирования производительности необходимо «шерстить» Интернет и научную литературу в поисках «тайных знаний» об особенностях планировщика и флагах ядра. Некоторые из этих параметров могут радикально влиять на результаты работы конкретного пакета тестов, в то время как другие могут потребовать несоразмерных усилий на то, чтобы в итоге узнать, что они вообще не влияют на ваши тесты.

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

Экранирование процессоров и привязка процессов

Экранирование процессоров — это технология, использующая псевдофайловую систему Linux cpuset для настройки эксклюзивных процессоров и узлов памяти, защищенных от использования планировщиком Linux. Самый простой способ настроить и использовать экранирование процессоров — воспользоваться cset, удобной оболочкой Python для интерфейса cpuset. В Ubuntu cset можно установить, выполнив следующую команду:

➜ sudo apt-get install cpuset

Нелишним будет ознакомиться с полным руководством по cset, доступным в RTwiki. Вот небольшой пример того, как можно экранировать процессоры 1 и 3 от нежелательных потоков (включая большинство потоков ядра, заданных флагом -k on):

➜ sudo cset shield -c 1,3 -k on
cset: --> activating shielding:
cset: moving 67 tasks from root into system cpuset...
[==================================================]%
cset: kthread shield activated, moving 91 tasks into system cpuset...
[==================================================]%
cset: **> 34 tasks are not movable, impossible to move
cset: "system" cpuset of CPUSPEC(0,2) with 124 tasks running
cset: "user" cpuset of CPUSPEC(1,3) with 0 tasks running

После настройки экранирования можно запускать процессы под его защитой с помощью флага -e (обратите внимание, что аргументы для процесса должны указываться после разделителя --):

➜ sudo cset shield -e echo -- "hello from within the shield"
cset: --> last message, executed args into cpuset "/user", new pid is: 27782
hello from within the shield
➜ sudo cset shield -e julia -- benchmark.jl
cset: --> last message, executed args into cpuset "/user", new pid is: 27792
running benchmarks...

Для немного более низкоуровневого управления можно использовать другие подкоманды cset, proc и set. Фактический интерфейс ядра cpuset предлагает еще больше возможностей, в частности жесткое ограничение доступа к памяти и настройки планирования.

Для обеспечения максимальной согласованности испытаний отдельные потоки, выполняемые под защитой экранирования, всегда должны использовать одну и ту же конфигурацию процессора и узлов памяти. Этого можно добиться за счет использования иерархических наборов ЦП (cpuset) для привязки процессов к дочерним cpuset, созданным в экранированном cpuset. Другие служебные средства для управления привязкой процессов, такие как taskset, numactl или tuna, не так полезны, как cset, поскольку они не защищают выделенные ресурсы от использования планировщиком.

Настройки виртуальной памяти

В официальной документации Linux перечислено множество параметров виртуальной памяти для настройки подкачки, страничной организации и кэширования в Linux. Я рекомендую читателям самостоятельно изучить свойства vm.nr_hugepages, vm.vfs_cache_pressure, vm.zone_reclaim_mode и vm.min_free_kbytes. Подробно останавливаться на них я не буду, поскольку в большинстве случаев они вряд ли окажут существенное влияние. Вместо этого я сосредоточусь на двух свойствах, с которыми проще экспериментировать и которые могут иметь менее очевидные последствия: приоритет подкачки (swappiness) и рандомизацию размещения адресного пространства.

Приоритет подкачки

В большинстве дистрибутивов Linux по умолчанию настроена агрессивная подкачка, что может существенно искажать результаты тестирования производительности из-за высокой вероятности использования подкачки во время выполнения тестов. К счастью, склонность ядра к использованию подкачки легко снизить, уменьшив значение приоритета подкачки, который регулируется с помощью параметра vm.swappiness:

➜ sudo sysctl vm.swappiness=10

По моему опыту, снижения значения vm.swappiness примерно до 10 достаточно для подавления связанного с подкачкой шума при выполнении большинства тестов производительности, зависящих от памяти.

Рандомизация размещения адресного пространства (ASLR)

Рандомизация размещения адресного пространства (ASLR) — это функция безопасности, которая затрудняет использование переполнения буфера вредоносными программами. Теоретически ASLR может существенно влиять на воспроизводимость результатов тестов производительности, которые очень чувствительны к изменениям в структуре памяти. Если вы решите отключить ASLR, то делаете это на свой страх и риск — в конце концов, это функция безопасности.

ASLR можно отключить глобально, присвоив параметру randomize_va_space значение 0:

➜ sudo sysctl kernel.randomize_va_space=0

Если вы не хотите отключать ASLR глобально, то можете просто запустить оболочку с отключенным ASLR, выполнив следующую команду:

➜ setarch $(uname -m) -R /bin/sh

Масштабирование и повышение частоты ЦП

Большинство современных процессоров поддерживают динамическое масштабирование частоты, то есть возможность регулировать тактовую частоту для управления энергопотреблением и температурой. В Linux масштабирование частоты контролируется эвристическими средствами, называемыми «диспетчерами», каждое из которых отдает приоритет определенной модели использования ресурсов. Эта функция может повлиять на результаты тестирования производительности, если масштабирование происходит во время тестирования или между испытаниями. Однако, к счастью, мы можем зафиксировать эффективную тактовую частоту, включив соответствующий диспетчер performance для всех процессоров:

➜ echo "performance" | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

Чтобы убедиться в том, что эта команда работает, проверьте, выдает ли cat /proc/cpuinfo | grep 'cpu MHz' те же значения, что и cat /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_max_freq.

Многие процессоры также поддерживают произвольное «повышение» производительности, что аналогично динамическому масштабированию частоты и может оказывать такое же негативное влияние на воспроизводимость результатов тестов производительности. Чтобы отключить ускорение работы процессора, можно выполнить следующую команду:

➜ echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost

Гиперпоточность

Гиперпоточность, более известная как одновременная многопоточность (SMT), позволяет нескольким программным потокам «одновременно» выполняться в «независимых» аппаратных потоках на одном ядре ЦП. Недостаток заключается в том, что на практике эти потоки не всегда могут выполняться одновременно, поскольку они конкурируют за общие ресурсы ЦП. К сожалению, Linux предоставляет доступ к этим потокам операционной системы как к дополнительным логическим процессорам, что затрудняет применение таких методов, как экранирование, — как узнать, что экранированный «процессор» на самом деле не использует то же физическое ядро, что и неэкранированный? Если ваша ситуация не требует выполнения тестов в среде с гиперпоточностью, возможно, будет лучше отключить гиперпоточность, чтобы было легче согласованно управлять ресурсами процессора.

Прежде чем отключать гиперпоточность, следует проверить, включена ли она на вашем компьютере. Это можно сделать с помощью lscpu:

➜ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8
On-line CPU(s) list:   0-7
Thread(s) per core:    2
Core(s) per socket:    4
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 60
Stepping:              3
CPU MHz:               3501.000
BogoMIPS:              6999.40
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              8192K
NUMA node0 CPU(s):     0-7

В приведенных выше выходных данных поле CPU(s) указывает, что имеется 8 логических процессоров. Другие поля позволяют провести более детальный анализ: Умножаем 1 сокет на 4 ядра и получаем 4 физических ядра. Умножаем их на 2 потока на ядро и получаем 8 логических процессоров. Логических процессоров больше, чем физических ядер, значит, гиперпоточность включена.

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

➜ cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list
0,4
1,5
2,6
3,7
0,4
1,5
2,6
3,7

Каждая строка выше имеет формат i,j, что означает logical processor i shares a physical core with logical processor j. Мы можем отключить гиперпоточность, деактивировав лишние процессоры и оставив только один логический процессор на каждое физическое ядро. В нашем примере для этого можно отключить процессоры 4, 5, 6 и 7:

➜ echo 0 | sudo tee /sys/devices/system/cpu/cpu4/online
0
➜ echo 0 | sudo tee /sys/devices/system/cpu/cpu5/online
0
➜ echo 0 | sudo tee /sys/devices/system/cpu/cpu6/online
0
➜ echo 0 | sudo tee /sys/devices/system/cpu/cpu7/online
0

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

➜ cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list
0
1
2
3

Запросы прерывания и привязка SMP

Ядро периодически отправляет процессорам запросы прерывания (IRQ). Как следует из названия, IRQ запрашивает у процессора приостановку выполнения текущей задачи для выполнения запрошенной задачи. Существует множество различных типов IRQ, и степень влияния определенного типа IRQ на тест производительности зависит от частоты и продолжительности выполнения IRQ по сравнению с рабочей нагрузкой теста.

Хорошо то, что для большинства типов IRQ можно задать привязку SMP, указав таким образом, какому процессору ядро должно отправлять IRQ. Если правильно настроить привязку SMP, запросы IRQ будут отправляться неэкранированным процессорам в среде тестирования, что защитит экранированные процессоры от нежелательных прерываний.

Используя псевдофайловую систему Linux proc, можно получить список прерываний, произошедших в системе с момента последней перезагрузки:

➜ cat /proc/interrupts
           CPU0       CPU1
  0:         19          0  IR-IO-APIC-edge      timer
  8:          1          0  IR-IO-APIC-edge      rtc0
  9:          0          0  IR-IO-APIC-fasteoi   acpi
 16:         27          0  IR-IO-APIC-fasteoi   ehci_hcd:usb1
 22:         12          0  IR-IO-APIC-fasteoi   ehci_hcd:usb2
 ⋮
 53:   18021763     122330  IR-PCI-MSI-edge      eth0-TxRx-7
NMI:      15661      13628  Non-maskable interrupts
LOC:  140221744   85225898  Local timer interrupts
SPU:          0          0  Spurious interrupts
PMI:      15661      13628  Performance monitoring interrupts
IWI:   23570041    3729274  IRQ work interrupts
RTR:          7          0  APIC ICR read retries
RES:    3153272    4187108  Rescheduling interrupts
CAL:       3401      10460  Function call interrupts
TLB:    4434976    3071723  TLB shootdowns
TRM:          0          0  Thermal event interrupts
THR:          0          0  Threshold APIC interrupts
MCE:          0          0  Machine check exceptions
MCP:      61112      61112  Machine check polls
ERR:          0
MIS:          0

Некоторые прерывания, например немаскируемые (NMI), нельзя перенаправить, но для остальных можно изменить привязки SMP, записав индексы процессора в /proc/irq/n/smp_affinity_list, где n — номер IRQ. Вот пример установки привязки SMP для IRQ 22 к процессорам 0, 1 и 2:

➜ echo 0-2 | sudo tee /proc/irq/22/smp_affinity_list

Наилучший способ настройки привязок SMP во многом зависит от конкретных тестов производительности и способа их выполнения. Например, если вы выполняете много сетевых тестов производительности, иногда может быть выгоднее равномерно распределять прерывания драйвера Ethernet (обычно обозначаемые как eth0-* или иным подобным образом), чем ограничивать их определенными процессорами.

Для определения влияния запросов IRQ на результаты теста производительности можно провести выборочную проверку, посмотрев, что происходит при включении и отключении балансировщика нагрузки IRQ, например irqbalance. Если это заметно влияет на результаты, возможно, стоит поэкспериментировать с привязками SMP, чтобы выяснить, какие прерывания не следует направлять экранированным процессорам.

Прерывания мониторинга производительности (PMI) и perf

Прерывания мониторинга производительности (PMI) отправляются подсистемой perf ядра, которая используется для настройки и управления аппаратными счетчиками производительности, отслеживаемыми другими компонентами ядра. Если perf не является зависимостью процесса тестирования производительности, может быть полезно снизить частоту выборки perf, чтобы прерывания PMI не мешали экспериментам. Это можно сделать, например, присвоив параметру kernel.perf_cpu_time_max_percent значение 1:

➜ sudo sysctl kernel.perf_cpu_time_max_percent=1

В результате ядро сообщает perf о необходимости снизить частоту выборки так, чтобы выборка занимала менее 1 % процессорного времени. После изменения этого параметра в системном журнале могут появиться такие сообщения:

[ 3835.065463] perf samples too long (2502 > 2500), lowering kernel.perf_event_max_sample_rate

Ничего страшного здесь нет — ядро просто сообщает, что оно снижает максимальную частоту выборки perf с учетом установленного свойства perf_cpu_time_max_percent.

Дополнительные материалы