3

За последние несколько месяцев у меня возникла чрезвычайно раздражающая проблема с моей системой Linux: она заикается при воспроизведении аудио в Firefox, перемещении мыши и т.д., С небольшим скачком в секунду (но все же заметным) каждые несколько секунд. Проблема ухудшается кэш - памяти заполняется, или когда у меня сильно диска / памяти интенсивных программ , работающих (например , программное обеспечение резервного копирования restic Однако, когда кеш не заполнен (например, при очень небольшой нагрузке), все работает очень гладко.

Просматривая вывод perf top , я вижу, что list_lru_count_one имеет высокие накладные расходы (~ 20%) в эти периоды лага. htop также показывает kswapd0 используя процессор на 50-90% (хотя кажется, что влияние намного выше). В периоды крайней задержки в измерителе ЦП htop часто преобладает использование ЦП ядра.

Единственный найденный мной обходной путь - либо заставить ядро оставить свободную память (sysctl -w vm.min_free_kbytes=1024000), либо постоянно отбрасывать кеши памяти через echo 3 > /proc/sys/vm/drop_caches . Конечно, ни один из них не идеален, и ни один не полностью решает заикание; это только делает это менее частым.

У кого-нибудь есть идеи о том, почему это может происходить?

Системная информация

  • i7-4820k с 20 ГБ (несовпадающей) оперативной памяти DDR3
  • Воспроизводится в Linux 4.14-4.18 в нестабильной среде NixOS
  • Запускает Docker-контейнеры и Kubernetes в фоновом режиме (что, как я чувствую, не должно создавать микрострукание?)

Что я уже пробовал

  • Изменение планировщиков ввода / вывода (bfq) с использованием многозадачных планировщиков ввода / вывода
  • Использование -ck от Con Kolivas (не помогло)
  • Отключение подкачки, изменение подкачки, использование zram

РЕДАКТИРОВАТЬ: Для ясности, вот изображение htop и perf во время такого скачка задержки. Обратите внимание на высокую загрузку процессора list_lru_count_one и высокую загрузку процессора ядром kswapd0 +.

вывод htop и perf

2 ответа2

2

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

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

  • vm.dirty_ratio: контролирует, сколько записей должно быть отложено для обратной записи, прежде чем она будет запущена. Обрабатывает обратную запись переднего плана (для каждого процесса) и выражается в виде целого процента ОЗУ. По умолчанию 10% оперативной памяти
  • vm.dirty_background_ratio: контролирует, сколько записей должно быть в ожидании обратной записи, прежде чем она будет запущена. Обрабатывает фоновую (общесистемную) обратную запись и выражается в виде целого процента ОЗУ. По умолчанию 20% оперативной памяти
  • vm.dirty_bytes: то же, что и vm.dirty_ratio , за исключением того, что выражается как общее количество байтов. Будет использоваться либо this, либо vm.dirty_ratio , в зависимости от того, что написано до конца .
  • vm.dirty_background_bytes: то же, что vm.dirty_background_ratio , за исключением того, что выражается как общее количество байтов. Будет использоваться либо this, либо vm.dirty_background_ratio , в зависимости от того, что написано до конца .
  • vm.dirty_expire_centisecs: сколько сотых долей секунды должно пройти, прежде чем начнется отложенная обратная запись, когда вышеупомянутые четыре значения sysctl еще не сработают. По умолчанию 100 (одна секунда).
  • vm.dirty_writeback_centisecs: как часто (в сотых долях секунды) ядро будет оценивать грязные страницы для обратной записи. По умолчанию 10 (одна десятая секунды).

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

  • Запишите любые измененные страницы в постоянное хранилище, если они были последний раз изменены более секунды назад.
  • Запишите все измененные страницы для процесса, если его общий объем измененной памяти, который не был записан, превышает 10% ОЗУ.
  • Запишите все измененные страницы в системе, если общий объем измененной памяти, который не был записан, превышает 20% ОЗУ.

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

В настоящее время общее согласие заключается в том, чтобы vm.dirty_ratio был равен 1% ОЗУ, а vm.dirty_background_ratio - 2%, что для систем с менее чем 64 ГБ ОЗУ приводит к поведению, эквивалентному первоначальному.

Некоторые другие вещи, чтобы посмотреть на:

  • Попробуйте немного увеличить sysctl vm.vfs_cache_pressure . Это контролирует, насколько агрессивно ядро восстанавливает память из кэша файловой системы, когда ему требуется оперативная память. По умолчанию 100, не опускайте его на что - нибудь ниже 50 (вы получите действительно плохое поведение , если вы идете ниже 50, в том числе условия OOM), и не поднимать его гораздо больше , чем около 200 (гораздо выше, и ядро будет тратить время, пытаясь восстановить память, которую он действительно не может). Я обнаружил, что увеличение его до 150 на самом деле заметно улучшает скорость отклика, если у вас достаточно быстрое хранилище.
  • Попробуйте изменить режим переполнения памяти. Это можно сделать, изменив значение sysctl vm.overcommit_memory . По умолчанию ядро будет использовать эвристический подход, чтобы попытаться предсказать, сколько ОЗУ он может выделить. Установка этого значения в 1 отключает эвристику и заставляет ядро действовать так, как будто оно имеет бесконечную память. Если установить значение 2, ядро не будет выделять больше памяти, чем общий объем пространства подкачки в системе, плюс процент фактической оперативной памяти (контролируется vm.overcommit_ratio).
  • Попробуйте vm.page-cluster sysctl. Это контролирует, сколько страниц будет выгружено или выгружено за раз (это логарифмическое значение base-2, поэтому значение по умолчанию 3 переводит в 8 страниц). Если вы на самом деле меняете местами, это может помочь повысить производительность обмена страницами.
1

Проблема была найдена!

Оказывается, что это проблема с производительностью в памяти Linux, когда имеется большое количество контейнеров / групп памяти. (Отказ от ответственности: мое объяснение может быть ошибочным, я не разработчик ядра.) Проблема была исправлена в 4.19-rc1+ в этом наборе патчей:

Этот набор исправлений решает проблему с медленным shrink_slab(), возникающим на машинах, имеющих много сжатых машин и cgroups памяти (то есть, со многими контейнерами). Проблема в том, что shrink_slab() имеет сложность O (n ^ 2) и растет слишком быстро с ростом числа контейнеров.

Пусть у нас будет 200 контейнеров, и у каждого контейнера 10 монтирований и 10 групп. Все задачи контейнера изолированы и не затрагивают монтирование сторонних контейнеров.

В случае глобального восстановления, задача должна выполнить итерацию по всем memcgs и вызвать все усматривающие memcg сокращения для всех из них. Это означает, что задача должна посещать 200 * 10 = 2000 сокращателей для каждой memcg, и, поскольку существует 2000 memcgs, общее количество вызовов do_shrink_slab() составляет 2000 * 2000 = 4000000.

Моя система пострадала особенно сильно, так как я запустил большое количество контейнеров, что, вероятно, и стало причиной появления проблемы.

Мои шаги по устранению неполадок, если они полезны для тех, кто сталкивается с подобными проблемами

  1. Обратите внимание, что kswapd0 использует тонну процессора, когда мой компьютер заикается
  2. Попробуйте остановить контейнеры Docker и снова заполнить память → компьютер не заикается!
  3. Запустите ftrace (после великолепного блога с объяснениями Джулии Эван), чтобы увидеть трассировку, посмотрите, что kswapd0 имеет тенденцию зависать в shrink_slab , super_cache_count и list_lru_count_one .
  4. Google shrink_slab lru slow , найдите набор патчей!
  5. Переключитесь на Linux 4.19-rc3 и убедитесь, что проблема устранена.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .