Кадр из фильма «Матрица: Революция»
В этой статье мы подробно рассмотрим детали одной интересной находки: два часто используемых системных вызова (gettimeofday
, clock_gettime
) в AWS EC2 выполняются очень медленно.
В Linux реализован механизм по ускорению этих двух часто используемых системных вызовов, благодаря которому их код выполняется в пространстве пользователя, что позволяет избежать переключениям в контекст ядра. Это сделано с помощью предоставляемой ядром виртуальной общей библиотеки (virtual shared library), которая отображается в адресное пространство всех запущенных программ.
Два вышеназванных системных вызова не могут использовать vDSO (virtual Dynamic Shared Object) в AWS EC2, поскольку виртуализированный источник временных меток (virtualized clock source) в xen (и некоторых конфигурациях kvm) не поддерживает получение информации о времени через vDSO.
Обойти эту проблему не получится. Можно поменять источник информации о времени на tsc
, но это небезопасно. Далее мы рассмотрим вопрос более подробно и проведем сравнительное тестирование с помощью microbenchmark.
Проверка
Чтобы быстро проверить систему на наличие этой проблемы, скомпилируйте приведенную ниже программу и выполните ее с strace
:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
int
main(int argc, char *argv[])
{
struct timeval tv;
int i = 0;
for (; i<100; i++) {
gettimeofday(&tv,NULL);
}
return 0;
}
gcc -o test test.c
strace -ce gettimeofday ./test
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 100 gettimeofday
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 100 total`
Strace
насчитала 100 вызовов gettimeofday
. Это значит, что vDSO не использовался, а вместо него делались реальные системные вызовы, что приводило к переключению в контекст ядра. Механизм vDSO в Linux был спроектирован с учетом gettimeofday
(этот вызов даже упомянут в man-странице vDSO). Любой системный вызов, сделанный через vDSO, полностью выполняется в пространстве пользователя, избегая тем самым переключения контекстов. В результате любой системный вызов, который успешно прошел через vDSO, не появится в выводе strace
.
Далее мы в подробностях разберем, почему и как это происходит, а также посмотрим на весьма интересные результаты профилирования.
Необходимая информация
Есть несколько важных аспектов, с которыми желательно ознакомиться, чтобы лучше понять описание проблемы и соответствующие фрагменты кода.
Системные вызовы Linux и vDSO
Прежде чем продолжать чтение этой статьи, настоятельно рекомендуем внимательно изучить нашу предыдущую публикацию, в которой детально описана работа системных вызовов Linux: The Definitive Guide to Linux System Calls (на английском языке).
vDSO по сути является предоставляемой ядром общей библиотекой, которая отображается в адресное пространство каждого процесса. Когда происходит вызов gettimeofday
, clock_gettime
, getcpu
или time
, glibc пытается выполнить код, предоставляемый vDSO. Этот код получает необходимые данные без переключения в контекст ядра и таким образом помогает избежать дополнительных расходов на выполнение настоящего системного вызова.
Поскольку сделанные через vDSO системные вызовы не приводят к переключению в контекст ядра, strace
не получает соответствующие уведомления. В результате в выводе strace
не будет упоминания о, например, gettimeofday
, если программа успешно сделала этот системный вызов через vDSO. Вместо strace
в этом случае нужно использовать ltrace
. Более подробную информацию о том, как устроена утилита strace
, можно найти в нашей публикации «How does strace work».
В AWS EC2 gettimeofday
появляется в выводе strace
. Это происходит потому, что vDSO в некоторых ситуациях выполняет обычные системные вызовы.
Время в Linux
В работающих под управлением Linux системах с архитектурой x86 для получения информации о времени используется несколько различных механизмов:
- программируемый таймер прерываний (Programmable Interrupt Timer);
- часы реального времени (Real-Time Clock);
- таймер событий высокой точности (High Precision Event Timer);
- локальный таймер APIC (local APIC timer);
- счетчик временных меток (Time Stamp Counter).
У каждого из названных механизмов есть свои плюсы и минусы. Подробную информацию можно найти в исходниках ядра Documentation/virtual/kvm/timekeeping.txt.
Важно понимать, что виртуализация создает дополнительные трудности при работе с информацией о времени. Например:
- Выполняющиеся на одном хосте виртуальные машины используют единый источник информации о времени, при этом обновить эту информацию на всех гостевых машинах в один и тот же момент невозможно. Более того, на некоторых виртуальных машинах во время работы критически важных частей ядра прерывания вообще могут быть отключены.
- Некоторые из механизмов работы со временем (например, Time Stamp Counter) уже изначально виртуализированы. Чтение из регистра TSC может влиять на производительность, приводя к получению неточных данных и отставанию часов (backwards time drift).
- Миграция виртуальных машин между гипервизорами с разными CPU может оказаться проблематичной, если система работы со временем завязана на тактовую частоту процессора.
Парни из VMWare опубликовали очень интересную статью, в которой описаны эти и другие вопросы, связанные с работой со временем. Информация в этой статье представлена как специфичная для VMWare, но по большому счета она относится к любой системе виртуализации.
Для решения этих и других проблем в KVM и Xen имеются собственные системы работы со временем: KVM PVclock и Xen time. В ядре Linux они называются clocksource (источник информации о времени).
Текущий clocksource системы может быть найден в файле /sys/devices/system/clocksource/clocksource0/current_clocksource
.
Именно к этому источнику обратится система при выполнении вызова gettimeofday
или clock_gettime
.
Резервный механизм vDSO
Давайте посмотрим, как вызов gettimeofday
реализован в коде vDSO. Напомню, что этот код идет в составе ядра, но выполняется в пространстве пользователя.
Если мы внимательно изучим код, расположенный в arch/x86/vdso/vclock_gettime.c, и сравним реализации gettimeofday
(__vdso_gettimeofday
) и clock_gettime
(__vdso_clock_gettime
) в vDSO, мы обнаружим, что в обоих случаях ближе к концу функции есть похожие блоки if:
if (ret == VCLOCK_NONE)
return vdso_fallback_gtod(clock, ts);
В коде __Vdso_clock_gettime
есть такая же проверка, но вызывается другая функция: vdso_fallback_gettime
.
Если ret
равен VCLOCK_NONE
, то это значит, что текущий системный источник информации о времени не поддерживает vDSO. В данном случае функция vdso_fallback_gtod
в обычном порядке выполняет системный вызов (переключаясь в контекст ядра со всеми вытекающими дополнительными расходами).
Но в каких случаях ret
получает значение VCLOCK_NONE
?
Если начать двигаться по коду вверх от этого блока условия, мы обнаружим, что ret
получает значение поля vclock_mode
текущего clocksource. В следующих источниках:
- High Precision Event Timer,
- Time Stamp Counter,
- и в некоторых случаях KVM PVClock
vclock_mode
не равен VCLOCK_NONE
.
С другой стороны, в источниках:
- Xen time,
- системы, где или в конфигурации ядра не включен параметр
CONFIG_PARAVIRT_CLOCK
, или процессор не предоставляет функциональность парвиртуализированных часов (paravirtualized clock feature)
vclock_mode
равен VCLOCK_NONE (0)
.
AWS EC2 использует Xen. В идущем по умолчанию в Xen clocksource (xen)
поле vclock_mode
установлено в VCLOCK_NONE
, поэтому инстансы EC2 всегда будут использовать медленные системные вызовы – механизм vDSO задействован не будет.
Но как это повлияет на производительность?
Разница в производительности между обычными и vDSO-системными вызовами
В этом эксперименте мы с помощью microbenchmark измерим, насколько быстрее gettimeofday
выполняется через vDSO по сравнению обычным системным вызовом.
Для этого мы запустим в EC2-инстансе тестовую программу с тремя циклами. Сначала будем тестировать с clocksource, равным xen
, а затем — tsc
.
Устанавливать clocksource равным tsc
в EC2 небезопасно. Маловероятно, но все же возможно, что это может привести непредвиденному отставанию часов (backwards clock drift). Не делайте этого в production-системах.
Условия эксперимента
Параметры AWS-инстанса:
- Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Type (AMI: ami-f173cc91),
- m4.xlarge instance size,
- зона us-west-2c.
Время выполнения мы будем измерять с помощью программы time
. Вы можете удивиться: «как можно использовать программу time
, которая способна дестабилизировать источник информации о времени (clocksource)?»
К счастью, разработчик ядра Ingo Molnar написал программу для определения фактов искажения времени (time warps): time-warp-test.c. Прошу заметить, что для работы на 64bit x86-системах программа должна быть немного изменена.
Во время проведения нашего эксперимента утилита time-warp-test
искажений времени не зафиксировала.
Для получения более обоснованного результата можно сделать следующее:
- Отставание часов (backward clock drift) маловероятно, но возможно. Многократное выполнение эксперимента и вероятностный анализ собранных данных может помочь отфильтровать некорректные значения.
- Эксперимент может быть повторен на невиртуализированных системах, которые не подвержены проблеме отставания или убегания часов (clock drift). Сначала можно протестировать работу через vDSO. Затем программа может быть изменена, чтобы делать системные вызовы напрямую.
Для целей нашего эксперимента было достаточно прогона тестов на искажения времени.
Результаты
Из результатов видно, что обычные системные вызовы в условиях ec2 примерно на 77% медленнее vDSO-вызовов:
5 миллионов вызовов gettimeofday
:
- vDSO включен:
- real: 0m0.123s
- user: 0m0.120s
- sys: 0m0.000s
- обычные системные вызовы:
- real: 0m0.547s
- user: 0m0.120s
- sys: 0m0.424s
50 миллионов вызовов gettimeofday
:
- vDSO включен:
- real: 0m1.225s
- user: 0m1.224s
- sys: 0m0.000s
- обычные системные вызовы:
- real: 0m5.459s
- user: 0m1.316s
- sys: 0m4.140s
500 миллионов вызовов gettimeofday
:
- vDSO включен:
- real: 0m12.247s
- user: 0m12.244s
- sys: 0m0.000s
- обычные системные вызовы:
- real: 0m54.606s
- user: 0m13.192s
- sys: 0m41.412s
Патчи для Xen на подходе
Чтобы исправить эту проблему, необходимо добавить поддержку vDSO в Xen. К счастью, в работе уже находится несколько соответствующих патчей.
Пока это (или аналогичное) изменение не попадет в ядро, а затем и в EC2, системные вызовы gettimeofday
и clock_gettime
будут выполняться на 77% медленнее, чем на аналогичных системах с поддержкой vDSO.
Заключение
Как и ожидалось, системные вызовы vDSO значительно быстрее обычных системных вызовов. Это достигается за счет того, что vDSO не переключается в контекст ядра. Важно помнить, что успешно выполненные системные вызовы vDSO не попадают в вывод strace
. Если vDSO использовать не удалось, будет сделан обычный системный вызов, который появится в выводе strace
.
В работе находится несколько патчей, которые призваны добавить поддержку vDSO в Xen, но не известно, когда эти изменения появятся в AWS EC2.
Пока этого не произошло, gettimeofday
и clock_gettime
будут выполняться примерно на 77% медленнее, чем должны были бы.
Использование strace
замедляет выполнение приложения, но дает бесценные сведения о том, что конкретно оно делает. Всем программистам следует прогонять свои приложения через strace
и анализировать вывод этой утилиты.
Статьи по теме
Если вам понравилась эта статья, рекомендую прочитать и другие наши публикации, в которых также содержится много низкоуровневой технической информации:
- Micro-optimizations matter: preventing 20 million system calls
- How setting the TZ environment variable avoids thousands of system calls
- Monitoring and Tuning the Linux Networking Stack: Sending Data
- Monitoring and Tuning the Linux Networking Stack: Receiving Data
- Illustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
- The Definitive Guide to Linux System Calls
How does strace
work?How does ltrace
work?- APT Hash sum mismatch
- HOWTO: GPG sign and verify deb packages and APT repositories
- HOWTO: GPG sign and verify RPM packages and yum repositories
Ссылки:
robert_ayrapetyan
Неужели кто-то использует EC2 (да и вообще виртуалки) для настолько критичного по производительности кода?