![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
В продолжение темы.
3. Ядерный планировщик.
Речь пойдет о дефолтном планировщике SCHED_ULE.
Начиная с 7.1-RELEASE, в системе есть команда cpuset, позволяющая привязывать исполнение процессов и отдельных тредов (включая треды ядра) к любому подмножеству ядер CPU, вплоть до ограничения единственным ядром. Разумеется, в системе есть и соответствующий API, доступный драйверам.
- Драйвер igb, создавая несколько очередей пакетов (см. предыдущий пост по ссылке выше), наивно привязывает очередь с номером Y к процессору с тем же номером, делая это одинаково для всех сетевых карт. В итоге, трафик PPPoE/GRE, приходящий через все сетевые карты igb, оказывается отнесен к нулевым очередям каждой сетевой и, в итоге, к нулевому ядру процессора. Это создает значительный дисбаланс в загрузке ядер, который, к счастью, легко исправить перепривязкой прерываний igb к различным ядрам CPU при использовании прямой обработки пакетов, без очереди netisr.
Команда возвращает количество процессорных ядер в системе, аsysctl -n kern.smp.cpus
vmstat -ai
показывает все прерывания, включая те, что используются очередями igb. Например, привязав очередь Y сетевого интерфейса igbX к процессору P=(X+Y)%N (где N - количество ядер CPU, а % - взятие остатка от деления по модулю N), мы получим разнесение нулевых очередей разных интерфейсов igb по разным ядрам. Ровно это и делает стартовый скрипт, который кладем в :/usr/local/etc/rc.d/cpuset-igb
#!/bin/sh # PROVIDE: cpuset-igb # REQUIRE: FILESYSTEMS # BEFORE: netif # KEYWORD: nojail case "$1" in *start) echo "Binding igb(4) IRQs to CPUs" cpus=`sysctl -n kern.smp.cpus` vmstat -ai | sed -E '/^irq.*que/!d; s/^irq([0-9]+): igb([0-9]+):que ([0-9]+).*/\1 \2 \3/' |\ while read irq igb que do cpuset -l $(( ($igb+$que) % $cpus )) -x $irq done ;; esac
Проверить привязку ядерных тредов к ядрам процессора можно командами
procstat -a -t
илиtop -SHPI
- При желании то же можно делать для драйвера em (проследив, чтобы наиболее занятые его треды попадали на другие ядра CPU).
- В версии 8.2 особой странностью отличается системный тред, исполняющий код dummynet. По умолчанию, он, как и большинство других тредов, не привязан к конкретному CPU и было замечено, что при средней или небольшой сетевой нагрузке планировщик перемещает его с CPU0 на CPU1 процессора Core i3 и обратно и на него тратится 86% времени одного ядра. А если привязать тред dummynet к CPU0, его потребление тут же падает до 0.0% и остается таким, пока сетевая нагрузка не станет расти к пиковым величинам. Причем работу свою dummynet продолжает выполнять, как и прежде, корректно, несмотря на то, что грузит собой CPU уже неощутимо мало. В пиковых нагрузках dummynet, привязанный к CPU0, потребляет в моих условиях немногим более 10% одного ядра.
Привязывается dummynet к CPU0 одной командойcpuset -l 0 -t $(procstat -t 0 | awk '/dummynet/ {print $2}')
Аналогичные странные эффекты наблюдаются с dummynet и на четырехядерных системах - привязка dummynet к CPU0 даёт нулевую загрузку CPU в непиковое время, перепривязка на CPU1 может тут же дать 80% загрузки, на CPU2 - 5%, на CPU3 около 1.5% (цифры неточные, но порядок величин сохранён). Эта проблема ещё ждет своего исправления, но обходной путь - привязка dummynet к CPU0 - вполне надежен. - Остальные процессы и треды привязывать вручную не пришлось, они ведут себя корректно при автоматической балансировке загрузки ядер планировщиком SCHED_ULE и загрузка ядер на моих конфигурациях получается приблизительно ровной.
В основном, это всё, что касается тюнинга 8.2 под "high-load mpd".