Почему планировщик Linux помещает два потока на одно физическое ядро на процессорах с HyperThreading?
Я читал в нескольких местах, что планировщик Linux по умолчанию с поддержкой hyperthreading на многоядерных машинах, что означает, что если у вас есть машина с 2 реальными ядрами (4 HT), она не будет планировать два занятых потока на логические ядра таким образом, чтобы они оба работали на одних и тех же физических ядрах (что во многих случаях приводило бы к 2-х издержкам).
Но когда я запускаю stress -c 2
(порождает два потока для запуска на 100% процессоре) на моем Intel i5-2520M, он часто планирует (и сохраняет) два потока на HT ядра 1 и 2, которые сопоставляются с одним и тем же физическим ядром. Даже если система не работает в противном случае.
Это также происходит с реальными программами (я использую stress
здесь, потому что это позволяет легко воспроизвести), и когда это происходит, моя программа, по понятным причинам, занимает в два раза больше времени для запуска. Настройка привязки вручную с помощью taskset
исправляет, что для моей программы, но я ожидал бы, что планировщик, поддерживающий HT, сделает это правильно сам по себе.
Вы можете найти HT- > физическое ядро с помощью egrep "processor|physical id|core id" /proc/cpuinfo | sed 's/^processor/\nprocessor/g'
.
Итак, мой вопрос: Почему планировщик помещает мои потоки в одно и то же физическое ядро здесь?
Примечания:
- Этот вопрос очень похож на этот другой вопрос, ответы на которые говорят, что Linux имеет довольно сложный планировщик потоков, который известен HT. Как описано выше, я не могу наблюдать этот факт (проверьте себя
stress -c
) и хотел бы знать, почему.
- Я знаю, что я могу настроить совместимость процессоров вручную для моих программ, например. с помощью инструмента
taskset
или с помощью функции sched_setaffinity
. Это не то, что я ищу, я бы ожидал, что планировщик сам знает, что отображение двух занятых потоков в физическое ядро и оставление одного физического ядра полностью пустым - не очень хорошая идея.
- Я знаю, что есть некоторые ситуации, в которых вы предпочли бы, чтобы потоки были запланированы на одно и то же физическое ядро и оставили другое ядро свободным, но кажется бессмысленно, что планировщик будет делать примерно 1/4 из этих случаев. Мне кажется, что ядра HT, которые он выбирает, являются полностью случайными, или, возможно, те HT-ядра, которые имели наименьшую активность во время планирования, но это не было бы очень гиперпотоком, учитывая, насколько ясно программы с характеристиками
stress
извлекайте выгоду из работы на отдельных физических ядрах.
Ответы
Ответ 1
Я думаю, что настало время обобщить некоторые знания из комментариев.
Linux-планировщик знает об HyperThreading - информация о нем должна быть прочитана из таблиц ACPI SRAT/SLIT, которые предоставляются BIOS/UEFI, - чем Linux строит домены планировщика.
Домены имеют иерархию - то есть на серверах с двумя процессорами вы получите три уровня доменов: all-cpus, per-cpu-package и per-cpu-core. Вы можете проверить его с помощью /proc/schedstat
:
$ awk '/^domain/ { print $1, $2; } /^cpu/ { print $1; }' /proc/schedstat
cpu0
domain0 0000,00001001 <-- all cpus from core 0
domain1 0000,00555555 <-- all cpus from package 0
domain2 0000,00ffffff <-- all cpus in the system
Часть планировщика CFS - это балансировка нагрузки - зверь, который должен красть задачи из вашего загруженного ядра в другое ядро. Вот его описание из документации ядра:
При этом он проверяет, исчерпал ли текущий домен его промежуток ребалансировки. Если это так, он запускает load_balance()
в этом домене. Затем он проверяет родительский sched_domain (если он существует) и родительский элемент родителя и так д.
Изначально load_balance()
находит самую занятую группу в текущем домене расписания. Если он преуспеет, он ищет самый загруженный runqueue всех запусков процессоров в эта группа. Если ему удастся найти такой runqueue, он блокирует и наши начальные CPU runqueue и вновь найденный самый загруженный и запускает из него задачи к нашему бегу. Точное количество задач составляет дисбаланс ранее вычисленный во время итерации по этим группам домена планирования.
От: https://www.kernel.org/doc/Documentation/scheduler/sched-domains.txt
Вы можете отслеживать действия балансировки нагрузки путем сравнения чисел в /proc/schedstat
. Я написал script для этого: schedstat.py
Счетчик alb_pushed
показывает, что балансировка нагрузки была успешно перенесена:
Sun Apr 12 14:15:52 2015 cpu0 cpu1 ... cpu6 cpu7 cpu8 cpu9 cpu10 ...
.domain1.alb_count ... 1 1 1
.domain1.alb_pushed ... 1 1 1
.domain2.alb_count 1 ...
.domain2.alb_pushed 1 ...
Однако логика балансировки нагрузки сложна, поэтому трудно определить, какие причины могут помешать ей хорошо выполнять свою работу и как они связаны с счетчиками schedstat. Ни я, ни @thatotherguy не могут воспроизвести вашу проблему.
Я вижу две возможности для этого поведения:
- У вас есть агрессивная политика энергосбережения, которая пытается сохранить одно ядро для снижения энергопотребления процессора.
- Вы действительно столкнулись с ошибкой с подсистемой планирования, чем вы должны пойти в LKML и тщательно поделиться своими выводами (включая
mpstat
и schedstat
данные)
Ответ 2
Я не могу воспроизвести это на 3.13.0-48 с моим процессором Intel (R) Xeon (R) E5-1650 0 @3.20GHz.
У меня есть 6 ядер с гиперпотоком, где логическое ядро N отображает физическое ядро N mod 6.
Здесь типичный вывод top
с stress -c 4
в двух столбцах, так что каждая строка является одним физическим ядром (я оставил несколько ядер, потому что моя система не простаивает):
%Cpu0 :100.0 us, %Cpu6 : 0.0 us,
%Cpu1 :100.0 us, %Cpu7 : 0.0 us,
%Cpu2 : 5.9 us, %Cpu8 : 2.0 us,
%Cpu3 :100.0 us, %Cpu9 : 5.7 us,
%Cpu4 : 3.9 us, %Cpu10 : 3.8 us,
%Cpu5 : 0.0 us, %Cpu11 :100.0 us,
Вот он после убийства и перезапуска stress
:
%Cpu0 :100.0 us, %Cpu6 : 2.6 us,
%Cpu1 :100.0 us, %Cpu7 : 0.0 us,
%Cpu2 : 0.0 us, %Cpu8 : 0.0 us,
%Cpu3 : 2.6 us, %Cpu9 : 0.0 us,
%Cpu4 : 0.0 us, %Cpu10 :100.0 us,
%Cpu5 : 2.6 us, %Cpu11 :100.0 us,
Я делал это несколько раз и не видел ни одного экземпляра, где 4 потока по 12 логическим ядрам планировались бы на одном физическом ядре.
С -c 6
Я имею тенденцию получать такие результаты, когда Linux, по-видимому, помогает в планировании других процессов на своих физических ядрах. Тем не менее, они распределены лучше, чем вероятность:
%Cpu0 : 18.2 us, %Cpu6 : 4.5 us,
%Cpu1 : 0.0 us, %Cpu7 :100.0 us,
%Cpu2 :100.0 us, %Cpu8 :100.0 us,
%Cpu3 :100.0 us, %Cpu9 : 0.0 us,
%Cpu4 :100.0 us, %Cpu10 : 0.0 us,
%Cpu5 :100.0 us, %Cpu11 : 0.0 us,
Ответ 3
Процитировав свой опыт с двумя дополнительными процессорами, которые, казалось, работали правильно, i7-2600 и Xeon E5-1620; Это может быть длинный, но как насчет обновления микрокода процессора? Он может включать в себя что-то, чтобы устранить проблему, если это внутреннее поведение процессора.
Загрузка микрокода Intel CPU: http://intel.ly/1aku6ak
Также см. здесь: https://wiki.archlinux.org/index.php/Microcode