Кадр из фильма «Матрица: Революция»


В этой статье мы подробно рассмотрим детали одной интересной находки: два часто используемых системных вызова (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.


Важно понимать, что виртуализация создает дополнительные трудности при работе с информацией о времени. Например:


  1. Выполняющиеся на одном хосте виртуальные машины используют единый источник информации о времени, при этом обновить эту информацию на всех гостевых машинах в один и тот же момент невозможно. Более того, на некоторых виртуальных машинах во время работы критически важных частей ядра прерывания вообще могут быть отключены.
  2. Некоторые из механизмов работы со временем (например, Time Stamp Counter) уже изначально виртуализированы. Чтение из регистра TSC может влиять на производительность, приводя к получению неточных данных и отставанию часов (backwards time drift).
  3. Миграция виртуальных машин между гипервизорами с разными 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. В следующих источниках:



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 и анализировать вывод этой утилиты.


Статьи по теме


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



Ссылки:


  1. Оригинал: Two frequently used system calls are ~77% slower on AWS EC2.
Поделиться с друзьями
-->

Комментарии (2)


  1. robert_ayrapetyan
    25.04.2017 06:45

    Неужели кто-то использует EC2 (да и вообще виртуалки) для настолько критичного по производительности кода?


  1. arheops
    25.04.2017 13:48

    блин. я думал реально долго, а тут 10млн+ в секунду вызовов. Не пугайте так.