Какие операции замедляют работу компьютера в многозадачных системах linux
Перейти к содержимому

Какие операции замедляют работу компьютера в многозадачных системах linux

  • автор:

Шедулер. Какие операции замедляют работу компьютера в многозадачных системах

Единственный в мире Музей Смайликов

Самая яркая достопримечательность Крыма
Скачать 15.88 Kb.

Какие операции замедляют работу компьютера в многозадачных системах?

Приведите ответ в свободной форме со своим комментарием.

Любые операции замедляют работу компьютера в большей или в меньшей степени, т.к. расходуют процессорное время и оперативную память. Но есть ещё прерывание и системные вызовы.

Прерывание (interrupt) — сигнал, сообщающий процессору о наступлении какого-либо события.

В зависимости от источника возникновения сигнала прерывания делятся на:

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

— синхронные, или внутренние — события в самом процессоре как результат нарушения каких-то условий при исполнении машинного кода: деление на ноль, обращение к недопустимым адресам памяти или недопустимый код операции;

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

Механизм прерываний поддерживается аппаратными и программными средствами ОС.

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

Системные вызовы являются интерфейсом между прикладными программами и ОС. Операционные системы предлагают процессам, работающим в режиме пользователя, набор интерфейсов для взаимодействия с аппаратными устройствами, такими как процессор, диски или принтеры. В Linux большинство интерфейсов между процессами режима пользователя и аппаратными устройствами реализовано с помощью системных вызовов, направляемых ядру.

В каких ситуациях планировщик ввода / вывода noop может быть производительней cfq ?

Использование ядра RT (низкая задержка)

Ядра RT разрешить оптимальная производительность в некоторых конкретные ситуацииНапример, редактирование аудио или использование виртуальных музыкальных инструментов.

Многозадачное ядро

Ядро Linux, как и большинство современных операционных систем, является многозадачным. Это означает, что одновременно работают несколько программ.

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

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

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

Переключение задач стоит дорого

Многозадачность не бесплатна: она связана с накладными расходами процессора. Действительно, исключение одной задачи и загрузка следующей — это дополнительная работа. Эта операция называется «переключением контекста» или «переключением задач». Было бы более экономично с точки зрения ЦП запускать программы полностью, одну за другой, чем разрезать их на «кусочки» и переходить от одной к другой. Однако система теряла бы интерактивность, мы не могли бы открыть несколько окон или, в случае сервера, обрабатывать несколько запросов одновременно.

Задержка и производительность

Предположим, нашему повару нужно очистить 20 кг креветок и удалить косточки с 20 кг оливок. Как планируется работа?

В крайнем случае, он сначала очистил все креветки, вымыл руки, чтобы избежать смешивания вкусов, а затем промыл все оливки. Изобразим это так:

GGGGGGGGGGGGGGGGGGGGG… C AAAAAAAAAAAAAAAAAAAAAA…

С другой стороны, он очистил бы креветку, вымыл бы руки, выложил оливковое масло, вымыл бы руки . креветки, маслины, креветки, маслины . Мы изобразим это так:

Буква C обозначает изменение контекста: мыть руки, менять посуду .

При этом официант собирает заявки клиентов: «одна с креветками!» . «одна с оливками!» . и переносит их на кухню.

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

Во втором случае, что бы ни попросил клиент, это будет доступно в короткие сроки, также практически одинаково в обоих случаях. Задержка будет низкой, но за это придется заплатить: из-за изменений контекста произойдет снижение производительности, понимаемое как часть времени, в течение которой ЦП выполняет непосредственно продуктивные задачи, а не задачи поддержки.

Очевидно, что в этом случае идеальным решением будет золотая середина, которая будет зависеть от размера рационов и статистического распределения запросов. Теория очередей — это раздел математики, который отвечает за изучение этих ситуаций и поиск оптимальных решений.

Как видите, задержка и производительность — противоположности. По этой причине нельзя сказать, что ядра RT дают большую производительность. Напротив, снижение задержки снижает производительность машины и поэтому является плохим выбором для систем, не требующих сверхбыстрых ответов, таких как веб-серверы или серверы баз данных.

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

Приоритеты

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

Ядро RT (или низкая задержка)

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

Проще говоря, ядра RT позволяют прерывать задачи в большем количестве мест, чем обычные ядра. Они могут делать, так сказать, более тонкие отрезки времени, поэтому текущая задача будет вытеснена быстрее, и наша приоритетная задача сможет быстрее получить доступ к ЦП. Следовательно, задержка будет ниже.

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

Помимо уменьшения толщины срезов, ядра RT имеют гораздо более строгую систему приоритетов, в которой приоритетные задачи беспощадно проталкивают друг друга (вытесняя), чтобы получить контроль над ЦП, замедляя другие программы по мере необходимости, чтобы удовлетворить ваши требования.

Когда важно использовать ядро ​​RT?

1) Когда нам нужны очень низкие задержки, то есть очень быстрая реакция машины. Самый яркий пример — исполнение виртуальных инструментов, когда вам нужно, чтобы при нажатии клавиши на MIDI-клавиатуре инструмент сразу звучал.

2) Когда нам нужны очень строгие приоритеты, то есть, чтобы наша высокоприоритетная задача не прерывалась ничем в мире (за исключением катастрофического случая, когда ЦП настолько перегружен, что превышает 100% загрузку). Например, мы записываем аудиосеанс с Ardor и наблюдаем, как индикаторы фейдеров поднимаются и опускаются. Не имеет значения, потеряем ли мы кадр обновления фейдеров, пока передача звука от микрофона на жесткий диск не прерывается. Ядро RT будет замедлять обновление фейдера настолько, насколько это необходимо, до тех пор, пока не будет потеряно ни одного сэмпла звука.

Тем не менее, в целом новые ядра без RT значительно улучшили планирование задач и управление приоритетами. Если у вас нет ЦП на пределе своих возможностей (скажем, загрузка ниже 50%) или если вы не возражаете, что время от времени в звуке есть небольшой микроперерез (щелчок) (очень страшные xruns), нормальное ядро ​​дает вполне приемлемую производительность.

Какая задержка желательна?

Лично меня устраивает все, что ниже 10 мс, а с 20 мс я уже начинаю четко замечать задержку. Есть люди более требовательные.

Установка

На Ubuntu и производных:

При запуске у вас будут оба варианта (обычное ядро ​​и ядро ​​с низкой задержкой).

В Arch и производных:

Содержание статьи соответствует нашим принципам редакционная этика. Чтобы сообщить об ошибке, нажмите здесь.

Полный путь к статье: Из Linux » ФайлДавайте использовать Linux » Использование ядра RT (низкая задержка)

Русские Блоги

Влияние переключения контекста процессора Linux на производительность

Несколько процессов, конкурирующих за ЦП, также увеличат среднюю нагрузку, что вызвано переключением контекста ЦП.

1. Контекст процессора

Linux — многозадачная операционная система. Система будет выделять временные интервалы ЦП для задач за очень короткий промежуток времени. Из-за короткого временного интервала у людей будет ощущение одновременной работы (десятки сотен раз в секунду) ,

Перед выполнением каждой задачи ЦП должен знать, откуда загружается задача и где запускать ЦП, а также восстанавливаться из регистра и переходить к месту, указанному счетчиком программы.

  1. Регистр ЦП: это память с небольшой встроенной емкостью, но очень быстрой скоростью.
  2. Счетчик программы: используется для сохранения позиции инструкции, выполняемой ЦПУ, или позиции следующей инструкции, которая будет выполнена.

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

Переключение контекста ЦП: сохранить контекст ЦП (регистр ЦП и программный счетчик) предыдущей задачи, затем загрузить контекст новой задачи в регистр и программный счетчик, перейти к месту, указанному программным счетчиком, и приступить к выполнению задачи.

Сценарий переключения контекста процессора делится на следующие сценарии в зависимости от различных задач:

  1. Переключение контекста процесса
  2. Переключение контекста потока
  3. Переключение контекста прерывания (вызвано аппаратным прерыванием)
  4. Переключение в привилегированном режиме (через системный вызов)

Во-вторых, переключатель режима привилегий

В соответствии с уровнем привилегий, Linux можно разделить на четыре уровня, обозначенных как 0-3. В большинстве операционных систем

  • Кольцо 0 представляет высший авторитет, пространство ядра и может напрямую обращаться ко всем ресурсам.
  • Кольцо 3 представляет пространство пользователя и не может напрямую обращаться к аппаратным устройствам, таким как память, оно захватывается системой через системные вызовы для доступа к этим привилегированным ресурсам.

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

Этот переключатель не переключает процессы и не задействует ресурсы виртуальной пользовательской памяти, такие как виртуальная память.

Три, переключение контекста процесса

Ресурсы процесса состоят из четырех частей:

  • Текстовый раздел содержит текст программы, то есть инструкции машинного языка, составленные из программы.
  • Статический раздел содержит переменные, выделенные компилятором, включая глобальные переменные и локальные переменные, объявленные с использованием static.
  • Секция стека содержит стек времени выполнения, который состоит из кадров стека, а каждый кадр стека содержит параметры функций, локальные переменные и другие.
  • Сегмент кучи содержит блоки памяти, выделенные во время выполнения, и обычно выделяется путем вызова функции стандартной библиотеки C malloc.

1. Потенциальная стоимость производительности процесса переключения контекста

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

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

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

Linux использует TLB (Translation Lookaside Buffer) для управления отношениями отображения между виртуальной памятью и физической памятью. Когда виртуальная память обновляется, TLB также должен быть обновлен, и доступ к памяти также будет соответственно замедляться. Особенно в многопроцессорной системе кэш-память используется несколькими процессорами, и обновление кеш-памяти не только повлияет на текущий процессорный процесс, но и на процессы других процессоров, совместно использующих кэш-память.

2. Когда будет запланирован запуск процесса на процессоре

Чтобы обеспечить правильное планирование всех процессов, время ЦП делится на сегменты временных сегментов, и эти временные сегменты затем распределяются для каждого процесса по очереди. Когда временной интервал процесса исчерпан, он будет приостановлен системой и переключен на другие процессы, ожидающие запуска ЦП.

Если у системы недостаточно системных ресурсов (например, недостаточно памяти), процесс не может быть запущен до тех пор, пока ресурсы не будут удовлетворены. В это время процесс также будет приостановлен, и система планирует другие процессы для запуска.

Когда процесс активно приостанавливается через функцию сна, он, естественно, будет перенесен.

Когда запущен процесс с более высоким приоритетом, чтобы обеспечить работу процесса с более высоким приоритетом, текущий процесс будет приостановлен и запущен процессом с более высоким приоритетом.

Когда происходит аппаратное прерывание, процесс в ЦП приостанавливается этим прерыванием, и вместо этого выполняется подпрограмма обслуживания прерывания в ядре.

Как только возникает проблема с производительностью переключения контекста, она вызывается описанным выше сценарием.

Четыре, переключение контекста потока

Процесс является основной единицей распределения ресурсов, а поток является основной единицей планирования ресурсов.

Планирование задач в ядре, фактический объект планирования является потоком, и процесс предоставляет потоку только ресурсы, такие как виртуальная память и глобальные переменные.

Когда процесс имеет только один поток, процесс можно считать равным потоку.

Когда процесс имеет несколько потоков, эти потоки будут использовать одну и ту же виртуальную память, глобальные переменные и другие ресурсы. Эти ресурсы не нужно изменять во время переключения контекста.

Потоки также имеют свои собственные личные данные, такие как стеки и регистры, которые необходимо сохранять во время переключения контекста.

Переключение контекста потока можно разделить на два случая

Два потока до и после принадлежат разным процессам. В это время, поскольку ресурсы не используются совместно, процесс переключения такой же, как и процесс переключения контекста.

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

Поэтому переключение потоков в одном и том же процессе потребляет меньше ресурсов, чем переключение между несколькими процессами, и это также является преимуществом многопоточности (пула потоков) вместо нескольких процессов.

Пять, прерывание переключения контекста

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

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

Контекст прерывания: включает только состояние, необходимое для выполнения программы обслуживания прерываний состояния ядра, включая регистры ЦП, стек ядра, параметры аппаратного прерывания и т. Д.

Для того же ЦП обработка прерываний имеет более высокий приоритет, чем процессы, поэтому переключение контекста прерывания не происходит одновременно с переключением контекста процесса. Кроме того, поскольку прерывания прерывают планирование и выполнение обычных процессов, большинство обработчиков прерываний являются короткими и лаконичными, поэтому выполнение может завершиться как можно быстрее.

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

Algo & DMA: технологии биржевой торговли

Биржевые технологии, алгоритмическая торговля, Java и программирование

Многозадачность операционной системы и low-latency

На современной серверной машине low-latency процесс работает на многозадачной операционной системе. Это может быть Solaris, чаще Linux, FreeBSD или Windows Server. Помимо нашего процесса в ОС работают еще и другие процессы, которым ОС тоже должна выделять машинное время. Собственно потому ОС и называется многозадачной.

В данной статье пойдет речь о том, как многозадачная операционная система и особенности ее архитектуры влияют на исполнение нашего процесса, и как мы можем побороть это влияние с целью достижения low-latency.

Переключение контекста (context switch)

Конкретный процесс работает определенное время до тех пор пока:

  • не произойдет вызов операции ввода-вывода (i/o operation). Тогда процесс останавливается и ждет окончания — поступления данных. Это может быть ожидание очередной порции байтов из сети или загрузка с диска каких-то данных или страницы памяти (page fault), или окончания записи данных в систему вывода: это может быть запись данных в сеть или запись их на диск (page swap);
  • не истечет выделенное ядром ОС время (time slice), после чего ядро приостанавливает исполнение процесса. Такое переключение происходит примерно каждые 20

В каждый конкретный момент времени процессор способен выполнять код только одного процесса (о многоядерных процессорах мы поговорим чуть позднее). За переключение процессов/потоков в современной многозадачной ОС отвечает специальный модуль ядра, который называется «планировщик» (scheduler).

Следуя определенным правилам он формирует очередь из ожидающих процессов/потоков и при окончании time slice одного процесса выбирает следующий процесс из очереди, выделяет ему определенный time slice и запускает его на исполнение. Для переключения процессов, надо:

  • остановить процесс
  • сохранить где-то его текущее состояние
  • загрузить состояние следующего процесса
  • начать исполнение следующего процесса с той точки, где он был недавно остановлен

Состояние процесса в конкретный момент времени называется «контекстом» (context). Это состояние описывается значениями регистров и счетчиков процессора в данный момент времени. Если эти все значения сохранить, а потом позже их загрузить, то процесс даже не поймет и не заметит, что его останавливали на какое-то время, он продолжит работать так, словно никакой остановки и не было. Этот процесс называется «переключением контекста» (context switch).

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

В Windows и Solaris потоки поддерживаются по-другому, поэтому речь о них идти не будет. В Linux, если наше приложение — многопоточное, каждый поток для операционной системы является отдельным особым процессом. Переключение между потоками приложения для Linux выглядит точно также, как переключение между обычными процессами с одной существенной разницей. Context switch между потоками одного приложения выполняется быстрее, чем context switch между двумя независимыми процессами. Это связано с тем, что все потоки одного приложения имеют доступ ко всей памяти данного процесса, следовательно при переключении контекста не надо менять контекст памяти, а достаточно только поменять контекст стека. На это тратится намного меньше времени. Так что, если в очереди на исполнение процессы в планировщике стоят так:

proc1:thread1

proc1:thread2

proc2:thread7

proc1:thread6

то переключение контекста proc1:thread1 -> proc1:thread2 осуществится быстрее, чем переключение proc2:thread7 -> proc1:thread6.

Переключение контекста на многоядерных и многопроцессорных машинах

Ушло в прошлое время одноядерных процессоров. Сейчас все процессоры выпускаются в многоядерном варианте. По сути в одном корпусе процессора вы получается сразу несколько CPU, которые могут выполнять код одновременно независимо друг от друга. Если одноядерному процессору надо было быстро переключаться между процессорами для создания иллюзии их параллельного одновременного исполнения, то на многоядерном процессоре в каждый конкретный момент времени может исполняться несколько процессов одновременно, параллельно, по-настоящему без всяких иллюзий.

Машины серверного класса имеют два и более процессоров для обеспечения надежности. Причем каждый процессор еще и является многоядерным. Например, 2х-процессорная машина с 2-мя 16 ядерными процессорами способна одновременно, параллельно выполнять 32 процесса/потока. А если на процессорах включена функция гиперпоточности — то 64 процесса/потока! 20 лет назад для достижения такого результата нам бы понадобилась машина с 64 процессорами класса Pentium. И 20 лет назад такая машина считалась бы суперкомпьютером, стоила бы миллионы американских долларов и доступна была бы только оборонке или богатому научному учреждению.

Планировщик

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

С самой первой версии Linux 1991 года вплоть до версии 2.4 планировщик был прост, понятен и тривиален, но плохо масштабировался при большом количестве процессов и при работе на многопроцессорных машинах. В версии ядра 2.5 была предпринята попытка создания нового планировщика под названием O(1), который работал намного лучше, повторял в чем-то работу планировщиков других UNIX-систем, но все равно имел свои недостатки. В версии ядра 2.6 были предприняты попытки написать еще один планировщик, который бы работал лучше, чем O(1). Этот эксперимент завершился в версии ядра 2.6.23, когда был представлен планировщик Completely Fair Scheduler (или сокращенно CFS).

Перенос потока с одного ядра на другое

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

Например, дана двухпроцессорная машина с двумя 16 ядерными процессорами. Нумерация ядер в Linux будет выглядеть так CPU0, … CPU7 — ядра первого процессора, CPU8 … CPU15 — ядра второго процессора. Скажем, наш главный процесс работает изначально на CPU0 (т.е. первом ядре первого процессора). Это процесс порождает 6 потоков. Обычно операционная система пытается каждый поток запустить на отдельном ядре, т.е. все 7 потоков (родительский и 6 порожденных) займут ядра с CPU0 до CPU6. Но потом планировщик будет останавливать эти потоки, чтобы дать на каждом ядре исполнится коду других потоков. Если мы посмотрим на наше приложение скажем через 15 минут, мы заметим, что наши потоки «разбрелись» по всем доступным ядрам процессора: родительский оказался на CPU5, а какой-то из порожденных потоков — на CPU15 (т.е. на последнем ядре второго процессора).

Как это скажется на производительности?

Ивалидация кэша

Каждое ядро процессора оснащено кэшем первого (L1) и второго уровней (L2), а сам процессор имеет общий кэш третьего уровня (L3), которым пользуются все ядра данного процессора. В кэше хранится копия данных, недавно извлеченная из памяти. Если процесс был остановлен планировщиком и потом запущен на другом ядре, кэши этого ядра не будут иметь этой копии данных, так как на этом ядре они еще не извлекались. А данные в кэше, оставшиеся от предыдущего потока, ему не нужны.

Первым делом поток обратится к кэшам за данными, не найдет их так и произойдет событие, которое называется cache miss процессов остановится, полезет в кэш L1, потом в кэш L2, потом в кэш L3, потом в память, извлечет из нее данные, поместит их в кэш, и только тогда продолжит исполнение процесса. На это требуется определенное время. Это все приведет к небольшой задержке. На графиках производительности это все будет выглядеть как jitter.

Если потом поток будет снова перенесен планировщиком на новое ядро, вся история повторится, это снова приведет к задержке. Так как переключение процессов на современных компьютерах происходит каждые 20-50 миллисекунд, мы будем каждые 20-50 миллисекунд получать задержку только по той причине, что планировщик запускает наш процесс всякий раз на каком-то другом ядре. Хорошо, если данные, с которыми предстоит процессу работать окажутся в кэше хотя бы третьего уровня (L3), тогда задержка будет несколько десятков наносекнуд, а если в памяти? А если в той странице памяти, которая отсутствует в физической памяти и должна быть прочитана с диска (page fault)?

Система предсказания переходов

Подсистема предсказания переходов (branch prediction) все еще может хранить информацию о предыдущих исполнениях этого потока. Значит, когда этот же поток снова начинает исполняться на этом же ядре, процессору не придется собирать эту информацию с нуля.

Affinity и Isolation

В полном идеале хорошо было бы, чтобы каждый конкретный поток не делил ядро с другим потоком, т.е. количество активных потоков было бы равно количеству выделенных для процесса ядер. Как минимум было бы хорошо, чтобы каждый поток работал на своем ядре и не переносился планировщиком на другое ядро. Для решения этой проблемы есть несколько инструментов.

Affinity

Первый инструмент называется affinity. Дословно — «приклеивание» процесса определенному ядру. Об этом приеме можно найти достаточно подробное описание в Википедии. Благодаря affinity планировщик точно знает, что данный процесс после «пробуждения» должен работать только на этом ядре или ядрах, и ни на каких других.

Например, вот как функция affinity выглядит в Windows 10. Обратите внимание, что так мы управляем affinity процесса, но не потоков этого процесса:

В Task Manager правый клик мышкой на процессе вызывает
меню, в котором можно выбрать пункт Set affinity
В окне Processor affinity в списке ядер отметьте ядра или ядро, на котором вы разрешаете выполнять данный процесс

На Linux привязки процесса к процессору можно добиться с помощью утилиты taskset. Более тонкая привязка какого-то потока к определенному ядру уже осуществляется:

  • программно через JNI/C++: например, как это сделано в OpenHFT/ThreadAffinity
  • с помощью утилит isocpu и cgroups.

Первый вариант предпочтительнее, так как при перезапуске процесса, не требуется выстраивать affinity вручную. С помощью конфигурационного файла можно определить affinity каждого потока и при запуске эти настройки автоматически «разбросают» потоки по ядрам.

Isolation

Второй инструмент — изоляция ядер (isolation). Мало приклеить процесс к определенному ядру, надо еще сообщить планировщику, чтобы он не запускал на этом ядре другие процессы, которые могут «загрязнить» кэши процесссора своими данными. На Linux изоляция процессора под определенную задачу осуществляется с помощью утилиты isolcpus.

На двухпроцессорной машине можно отвести все ядра одного процессора под low-latency приложение, а все сопутствующие сервисы и приложения операционной системы — запускать на втором процессоре. Так с помощью affinity и изоляции в полном распоряжении вашего low-latency приложения окажется 16 ядер одного процессора. Если в вашем приложении 16 потоков, то каждый поток беспрерывно будет исполняться на своем ядре и не будет приводить к переключению контекстов.

Paging и Page Fault

Процесс в память загружается не целиком, в памяти процесса в конкретный момент находится только несколько сегментов кода по 4 килобайта каждый. Если ваш код в процессе исполнения делает переход на часть кода, которого нет в этих страницах, происходит так называемый «page fault», исполнение программы приостанавливается, ядро дозагружает нужную страницу с диска и потом исполнение продолжается дальше.

Swapping

Если все работающие процессы не помещаются в памяти, операционная система запускает механизм свопа: останавливает неактивные процессы и выгружает из целиком на диск. В случае если процесс снова надо вернуть в память и выполнить его, происходит обратная операция: весь процесс загружается в память и продолжает свое исполнение как если бы он там все время находился. Выгрузка процесса на диск и загрузка его с диска в память — очень дорогие операции. Всякий, кто когда-либо работал, например с Windows, на маленьком объеме памяти помнит, как при переключении между задачами система буквально замирала, а жесткий диск неистово дёргался.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *