Как расставить приоритеты (или установить политику планирования) для потоков «менеджер» и «работник» процесса?

Я запускаю процесс (на ОС Linux 3.x), в котором:

  • Несколько потоков являются потоками «менеджера» (для простоты предположим, что они принимают решения относительно того, что рабочие потоки должны делать, но не выполняют никаких операций ввода-вывода, и количество процессорного времени, в котором они нуждаются, в целом короче / намного короче рабочего потоки’)
  • Больше потоков — это «рабочие» потоки: они выполняют тяжелые вычисления, и у меня нет проблем с их прерыванием в любое время.

Возможно, что существует избыточная подписка (то есть больше рабочих потоков, чем вдвое больше ядер на процессоре Intel с HT). Теперь я вижу, что потоки «менеджера» не получают процессорного времени достаточно часто. Они не совсем «голодные», я просто хочу дать им толчок. Поэтому, естественно, я думал об установке различных приоритетов потоков (я работаю в Linux), но затем я заметил различные варианты планировщиков потоков и их влияние. В этот момент я запутался, а точнее — мне не понятно

  • Какую политику планирования выбрать для менеджеров, а какую для работников?
  • Что я должен установить приоритеты потоков (если вообще)?
  • Нужно ли, чтобы мои потоки иногда выдавали ()?

Заметки:

  • Я намеренно ничего не говорю о механизме языка или пула потоков. Я хочу задать этот вопрос в более общей обстановке.
  • Пожалуйста, не делайте предположений о ядрах процессора. Их может быть много, или, может быть, только один, и, возможно, мне нужны рабочие (или рабочие и менеджеры) в каждом ядре.
  • Рабочие потоки могут или не могут делать ввод / вывод. Ответы для случая, когда они не выполняют ввод-вывод, приветствуются.
  • Мне не нужно, чтобы система была очень отзывчивой, кроме запуска моего приложения. Я имею в виду, что я бы предпочел использовать SSH там, и мой набор печатался бы мне без значительной задержки, но никаких реальных ограничений там нет.

5

Решение

UPD 12.02.2015Я провел несколько экспериментов.

теория

Существует очевидное решение изменить планировщик потоков «менеджер» на RT (планировщик реального времени, который предоставляет политики SCHED_DEADLINE / SCHED_FIFO). В этом случае потоки «менеджера» всегда будут иметь больший приоритет, чем большинство потоков в системе, поэтому они почти всегда получают ЦП, когда им это необходимо.

Тем не менее, есть другое решение, которое позволяет вам оставаться на Планировщик CFS. Ваше описание назначения «рабочих» тем похоже на партия составление расписания (в древние времена, когда компьютеры были большими, пользователь должен поставить свою работу в очередь и часами ждать, пока она не будет выполнена). Linux CFS поддерживает пакетные задания через политику SCHED_BATCH, а задания диалога — через политику SCHED_NORMAL.

Также есть полезный комментарий в коде ядра (ядро / SCHED / fair.c):

/*
* Batch and idle tasks do not preempt non-idle tasks (their preemption
* is driven by the tick):
*/
if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION))
return;

Поэтому, когда «менеджерский» поток или какое-то другое событие пробуждает «рабочий», последний получит ЦП только в том случае, если в системе есть свободные ЦП или когда «менеджер» исчерпает свой временной интервал (чтобы настроить его, измените вес задачи).

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

эксперимент

У меня есть сервер с двумя процессорами Intel Xeon E5-2420, который дает нам 24 аппаратных потока. Для симуляции двух потоков я использовал свой собственный TSLoad генератор рабочей нагрузки (и исправление нескольких ошибок во время экспериментов :)).

Было два потоковых пула: tp_manager с 4-мя нитками и tp_worker с 30 нитями, оба работают busy_wait рабочие нагрузки (просто for(i = 0; i < N; ++i);) но с разным количеством циклов цикла. tp_worker работает в benchmark режим, поэтому он будет выполнять столько запросов, сколько может и занимает 100% процессорного времени.

Вот пример конфигурации: https://gist.github.com/myaut/ad946e89cb56b0d4acde

3.12 (ваниль с отладочным конфигом)

EXP  |              MANAGER              |     WORKER
|  sched            wait    service | sched            service
|  policy           time     time   | policy            time
33   |  NORMAL          0.045    2.620   |     WAS NOT RUNNING
34   |  NORMAL          0.131    4.007   | NORMAL           125.192
35   |  NORMAL          0.123    4.007   | BATCH            125.143
36   |  NORMAL          0.026    4.007   | BATCH (nice=10)  125.296
37   |  NORMAL          0.025    3.978   | BATCH (nice=19)  125.223
38   |  FIFO (prio=9)  -0.022    3.991   | NORMAL           125.187
39   |  core:0:0        0.037    2.929   | !core:0:0        136.719

3.2 (сток Debian)

EXP  |              MANAGER              |     WORKER
|  sched            wait    service | sched            service
|  policy           time     time   | policy            time
46   |  NORMAL          0.032    2.589   |     WAS NOT RUNNING
45   |  NORMAL          0.081    4.001   | NORMAL           125.140
47   |  NORMAL          0.048    3.998   | BATCH            125.205
50   |  NORMAL          0.023    3.994   | BATCH (nice=10)  125.202
48   |  NORMAL          0.033    3.996   | BATCH (nice=19)  125.223
42   |  FIFO (prio=9)  -0.008    4.016   | NORMAL           125.110
39   |  core:0:0        0.035    2.930   | !core:0:0        135.990

Некоторые заметки:

  • Часовой пояс в миллисекундах
  • Последний эксперимент предназначен для установки сходств (рекомендуется @ PhilippClaßen): потоки менеджера были связаны с Core # 0, в то время как рабочие потоки были связаны со всеми ядрами, кроме Core # 0.
  • Время обслуживания потоков менеджера увеличилось в два раза, что объясняется параллелизмом внутри ядер (процессор имеет Hyper-Threading!)
  • Использование SCHED_BATCH + nice (TSLoad не может напрямую установить вес, но nice может сделать это косвенно) немного сокращает время ожидания.
  • Отрицательное время ожидания в эксперименте SCHED_FIFO в порядке: TSLoad резервирует 30us, так что он может выполнить предварительную работу / у планировщика есть время для переключения контекста и т. Д. Кажется, что SCHED_FIFO очень быстрый.
  • Резервирование одного ядра не так уж и плохо, и, поскольку он удалил параллелизм в ядре, время обслуживания было значительно сокращено
7

Другие решения

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

Ссылка на сайт: Поток, привязывающий ядро ​​процессора

Явная уступка обычно не нужна, на самом деле часто не рекомендуется. Процитирую Роберта Лава в «Системном программировании Linux»:

На практике существует несколько законных применений sched_yield () в надлежащей упреждающей многозадачной системе, такой как Linux. Ядро полностью способно принимать оптимальные и наиболее эффективные решения по планированию — конечно, ядро ​​лучше оснащено, чем отдельное приложение, чтобы решить, что выгрузить и когда.

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

2

В дополнение к отличному ответу myaut’а стоит попробовать ядро ​​с примененным набором патчей CONFIG_PREEMPT_RT. Это вносит некоторые довольно серьезные изменения в то, как ядро ​​выполняет планирование, в результате чего задержка планирования становится намного более детерминированной.

Использование в сочетании с получением правильных относительных приоритетов потоков (менеджеры> рабочие) с любым предложением myaut (и особенно с SCHED_FIFO) может дать очень хорошие результаты.

2
По вопросам рекламы [email protected]