- PVSM.RU - https://www.pvsm.ru -

Кадр из фильма «Матрица: Революция»
В этой статье мы подробно рассмотрим детали одной интересной находки: два часто используемых системных вызова (gettimeofday, clock_gettime) в AWS EC2 выполняются очень медленно.
В Linux реализован механизм по ускорению этих двух часто используемых системных вызовов, благодаря которому их код выполняется в пространстве пользователя, что позволяет избежать переключениям в контекст ядра. Это сделано с помощью предоставляемой ядром виртуальной общей библиотеки (virtual shared library) [1], которая отображается в адресное пространство всех запущенных программ.
Два вышеназванных системных вызова не могут использовать 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 [2]). Любой системный вызов, сделанный через vDSO, полностью выполняется в пространстве пользователя, избегая тем самым переключения контекстов. В результате любой системный вызов, который успешно прошел через vDSO, не появится в выводе strace.
Далее мы в подробностях разберем, почему и как это происходит, а также посмотрим на весьма интересные результаты профилирования.
Есть несколько важных аспектов, с которыми желательно ознакомиться, чтобы лучше понять описание проблемы и соответствующие фрагменты кода.
Прежде чем продолжать чтение этой статьи, настоятельно рекомендуем внимательно изучить нашу предыдущую публикацию, в которой детально описана работа системных вызовов Linux: The Definitive Guide to Linux System Calls (на английском языке) [3].
vDSO по сути является предоставляемой ядром общей библиотекой, которая отображается в адресное пространство каждого процесса. Когда происходит вызов gettimeofday, clock_gettime, getcpu или time, glibc пытается выполнить код, предоставляемый vDSO. Этот код получает необходимые данные без переключения в контекст ядра и таким образом помогает избежать дополнительных расходов на выполнение настоящего системного вызова.
Поскольку сделанные через vDSO системные вызовы не приводят к переключению в контекст ядра, strace не получает соответствующие уведомления. В результате в выводе strace не будет упоминания о, например, gettimeofday, если программа успешно сделала этот системный вызов через vDSO. Вместо strace в этом случае нужно использовать ltrace. Более подробную информацию о том, как устроена утилита strace, можно найти в нашей публикации «How does strace work [4]».
В AWS EC2 gettimeofday появляется в выводе strace. Это происходит потому, что vDSO в некоторых ситуациях выполняет обычные системные вызовы.
В работающих под управлением Linux системах с архитектурой x86 для получения информации о времени используется несколько различных механизмов:
У каждого из названных механизмов есть свои плюсы и минусы. Подробную информацию можно найти в исходниках ядра Documentation/virtual/kvm/timekeeping.txt [5].
Важно понимать, что виртуализация создает дополнительные трудности при работе с информацией о времени. Например:
Парни из VMWare опубликовали очень интересную статью [6], в которой описаны эти и другие вопросы, связанные с работой со временем. Информация в этой статье представлена как специфичная для VMWare, но по большому счета она относится к любой системе виртуализации.
Для решения этих и других проблем в KVM и Xen имеются собственные системы работы со временем: KVM PVclock [7] и Xen time. В ядре Linux они называются clocksource (источник информации о времени).
Текущий clocksource системы может быть найден в файле /sys/devices/system/clocksource/clocksource0/current_clocksource.
Именно к этому источнику обратится система при выполнении вызова gettimeofday или clock_gettime.
Давайте посмотрим, как вызов gettimeofday реализован в коде vDSO. Напомню, что этот код идет в составе ядра, но выполняется в пространстве пользователя.
Если мы внимательно изучим код, расположенный в arch/x86/vdso/vclock_gettime.c [8], и сравним реализации 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 [9] в обычном порядке выполняет системный вызов (переключаясь в контекст ядра со всеми вытекающими дополнительными расходами).
Но в каких случаях ret получает значение VCLOCK_NONE?
Если начать двигаться по коду вверх от этого блока условия, мы обнаружим, что ret получает значение поля vclock_mode текущего clocksource. В следующих источниках:
vclock_mode не равен VCLOCK_NONE.
С другой стороны, в источниках:
CONFIG_PARAVIRT_CLOCK, или процессор не предоставляет функциональность парвиртуализированных часов (paravirtualized clock feature)vclock_mode равен VCLOCK_NONE (0).
AWS EC2 использует Xen. В идущем по умолчанию в Xen clocksource (xen) поле vclock_mode установлено в VCLOCK_NONE, поэтому инстансы EC2 всегда будут использовать медленные системные вызовы – механизм vDSO задействован не будет.
Но как это повлияет на производительность?
В этом эксперименте мы с помощью microbenchmark измерим, насколько быстрее gettimeofday выполняется через vDSO по сравнению обычным системным вызовом.
Для этого мы запустим в EC2-инстансе тестовую программу с тремя циклами. Сначала будем тестировать с clocksource, равным xen, а затем — tsc.
Устанавливать clocksource равным tsc в EC2 небезопасно. Маловероятно, но все же возможно, что это может привести непредвиденному отставанию часов (backwards clock drift). Не делайте этого в production-системах.
Параметры AWS-инстанса:
Время выполнения мы будем измерять с помощью программы time. Вы можете удивиться: «как можно использовать программу time, которая способна дестабилизировать источник информации о времени (clocksource)?»
К счастью, разработчик ядра Ingo Molnar написал программу для определения фактов искажения времени (time warps): time-warp-test.c [14]. Прошу заметить, что для работы на 64bit x86-системах программа должна быть немного изменена.
Во время проведения нашего эксперимента утилита time-warp-test искажений времени не зафиксировала.
Для получения более обоснованного результата можно сделать следующее:
Для целей нашего эксперимента было достаточно прогона тестов на искажения времени.
Из результатов видно, что обычные системные вызовы в условиях ec2 примерно на 77% медленнее vDSO-вызовов:
5 миллионов вызовов gettimeofday:
50 миллионов вызовов gettimeofday:
500 миллионов вызовов gettimeofday:
Чтобы исправить эту проблему, необходимо добавить поддержку vDSO в Xen. К счастью, в работе уже находится несколько соответствующих патчей [16].
Пока это (или аналогичное) изменение не попадет в ядро, а затем и в EC2, системные вызовы gettimeofday и clock_gettime будут выполняться на 77% медленнее, чем на аналогичных системах с поддержкой vDSO.
Как и ожидалось, системные вызовы vDSO значительно быстрее обычных системных вызовов. Это достигается за счет того, что vDSO не переключается в контекст ядра. Важно помнить, что успешно выполненные системные вызовы vDSO не попадают в вывод strace. Если vDSO использовать не удалось, будет сделан обычный системный вызов, который появится в выводе strace.
В работе находится несколько патчей, которые призваны добавить поддержку vDSO в Xen [16], но не известно, когда эти изменения появятся в AWS EC2.
Пока этого не произошло, gettimeofday и clock_gettime будут выполняться примерно на 77% медленнее, чем должны были бы.
Использование strace замедляет выполнение приложения, но дает бесценные сведения о том, что конкретно оно делает. Всем программистам следует прогонять свои приложения через strace и анализировать вывод этой утилиты.
Если вам понравилась эта статья, рекомендую прочитать и другие наши публикации, в которых также содержится много низкоуровневой технической информации:
How does strace work? [4]How does ltrace work? [22]Ссылки:
Автор: olemskoi
Источник [27]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/xen/253601
Ссылки в тексте:
[1] виртуальной общей библиотеки (virtual shared library): https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/#virtual-system-calls
[2] man-странице vDSO: http://man7.org/linux/man-pages/man7/vdso.7.html
[3] The Definitive Guide to Linux System Calls (на английском языке): https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/
[4] How does strace work: https://blog.packagecloud.io/eng/2016/02/29/how-does-strace-work/
[5] Documentation/virtual/kvm/timekeeping.txt: https://github.com/torvalds/linux/blob/v3.13/Documentation/virtual/kvm/timekeeping.txt
[6] очень интересную статью: http://www.vmware.com/pdf/vmware_timekeeping.pdf
[7] KVM PVclock: http://www.linux-kvm.org/page/KVMClock
[8] arch/x86/vdso/vclock_gettime.c: https://github.com/torvalds/linux/blob/v3.13/arch/x86/vdso/vclock_gettime.c#L260-L282
[9] функция vdso_fallback_gtod: https://github.com/torvalds/linux/blob/v3.13/arch/x86/vdso/vclock_gettime.c#L144-L151
[10] High Precision Event Timer: https://github.com/torvalds/linux/blob/v3.13/arch/x86/kernel/hpet.c#L755-L757
[11] Time Stamp Counter: https://github.com/torvalds/linux/blob/v3.13/arch/x86/kernel/tsc.c#L789-L791
[12] KVM PVClock: https://github.com/torvalds/linux/blob/v3.13/arch/x86/kernel/kvmclock.c#L305
[13] Xen time: https://github.com/torvalds/linux/blob/v3.13/arch/x86/xen/time.c#L233-L239
[14] time-warp-test.c: https://people.redhat.com/mingo/time-warp-test/time-warp-test.c
[15] делать системные вызовы напрямую: https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/#using-syscall-system-calls-with-your-own-assembly
[16] в работе уже находится несколько соответствующих патчей: https://lkml.org/lkml/2017/1/25/474
[17] Micro-optimizations matter: preventing 20 million system calls: https://blog.packagecloud.io/eng/2017/03/06/micro-optimizations-matter/
[18] How setting the TZ environment variable avoids thousands of system calls: https://blog.packagecloud.io/eng/2017/02/21/set-environment-variable-save-thousands-of-system-calls/
[19] Monitoring and Tuning the Linux Networking Stack: Sending Data: https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/
[20] Monitoring and Tuning the Linux Networking Stack: Receiving Data: https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/
[21] Illustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data: https://blog.packagecloud.io/eng/2016/10/11/monitoring-tuning-linux-networking-stack-receiving-data-illustrated/
[22] How does ltrace work?: https://blog.packagecloud.io/eng/2016/03/14/how-does-ltrace-work/
[23] APT Hash sum mismatch: https://blog.packagecloud.io/eng/2016/03/21/apt-hash-sum-mismatch/
[24] HOWTO: GPG sign and verify deb packages and APT repositories: https://blog.packagecloud.io/eng/2014/10/28/howto-gpg-sign-verify-deb-packages-apt-repositories/
[25] HOWTO: GPG sign and verify RPM packages and yum repositories: https://blog.packagecloud.io/eng/2014/11/24/howto-gpg-sign-verify-rpm-packages-yum-repositories/
[26] Two frequently used system calls are ~77% slower on AWS EC2: https://blog.packagecloud.io/eng/2017/03/08/system-calls-are-much-slower-on-ec2/
[27] Источник: https://habrahabr.ru/post/326298/
Нажмите здесь для печати.