Контекст переключается гораздо медленнее в новых ядрах linux
мы хотим обновить ОС на наших серверах с Ubuntu 10.04 LTS до Ubuntu 12.04 LTS. К сожалению, кажется, что задержка для запуска потока, который стал запускаемым, значительно увеличилась с ядра 2.6 до ядра 3.2. На самом деле цифры задержки, которые мы получаем, трудно поверить.
позвольте мне быть более конкретным о тесте. У нас есть программа, которая запускает два потока. Первый поток получает текущее время (в ТИКах с помощью RDTSC), а затем сигнал переменная условия один раз в секунду. Второй поток ожидает переменную условия и просыпается, когда она сигнализируется. Затем он получает текущее время (в ТИКах с помощью RDTSC). Разница между временем во втором потоке и временем в первом потоке вычисляется и отображается на консоли. После этого второй поток ожидает переменную условия еще раз. Он будет снова сигнализирован первым потоком примерно через секунду.
Итак, в двух словах мы получаем поток к потоку связи через переменную условия измерение задержки раз в секунду в результате.
в ядре 2.6.32 эта задержка составляет где-то порядка 2.8-3.5 us, что является разумным. В ядре 3.2.0 эта задержка увеличилась до где-то порядка 40-100 us. Я исключил любые различия в оборудовании между двумя хостами. Они работают на одинаковом оборудовании (двойной разъем процессоров X5687 {у Westmere-РД} работает на 3,6 ГГц с HyperThreading, speedstep и все состояния C отключены). Тестовое приложение изменяет сродство потоков для их запуска на независимых физических ядрах одного и того же сокета (т. е. первый поток запускается на ядре 0, а второй поток запускается на ядре 1), поэтому нет отскока потоков на ядрах или отскока/связи между сокетами.
единственное различие между двумя хостами заключается в том, что один из них работает под управлением Ubuntu 10.04 LTS с ядром 2.6.32-28 (переключатель быстрого контекста) , а другой работает последняя версия Ubuntu 12.04 LTS с ядром 3.2.0-23 (переключатель медленного контекста). Все настройки BIOS и аппаратное обеспечение идентичны.
были ли какие-либо изменения в ядре, которые могли бы объяснить это нелепое замедление в течение какого времени требуется для запланированного запуска потока?
обновление: Если вы хотите запустить тест на своем Хосте и сборке linux, у меня есть отправил код в pastebin для вашего сведения. Компилировать с:
g++ -O3 -o test_latency test_latency.cpp -lpthread
запуск с (предполагая, что у вас есть по крайней мере двухъядерный блок):
./test_latency 0 1 # Thread 1 on Core 0 and Thread 2 on Core 1
обновление 2: После долгих поисков параметров ядра, сообщений об изменениях ядра и личных исследований я понял, в чем проблема, и опубликовал решение в качестве ответа на этот вопрос.
3 ответа:
решение bad thread wake up performance problem в последних ядрах имеет отношение к переключению на
intel_idle
драйвер cpuidle отacpi_idle
, драйвер, используемый в старых ядрах. К сожалению,intel_idle
драйвер игнорирует конфигурацию BIOS пользователя для C-состояний и танцует под свою дудку. Другими словами, даже если вы полностью отключите все состояния C в BIOS вашего ПК (или сервера), этот драйвер все равно заставит их включаться в течение коротких периодов бездействие, которое почти всегда происходит, если не работает все ядро, потребляющее синтетический бенчмарк (например, стресс). Вы можете отслеживать переходы состояния C, а также другую полезную информацию, связанную с частотами процессора, используя замечательный Google инструмент i7z на большинстве совместимых аппаратных средств.чтобы увидеть, какой драйвер cpuidle в настоящее время активен в вашей настройке, просто cat the на на
/sys/devices/system/cpu
как следует:cat /sys/devices/system/cpu/cpuidle/current_driver
если вы хотите, чтобы ваша современная ОС Linux имела наименьшую возможную задержку переключения контекста, добавьте следующие параметры загрузки ядра, чтобы отключить все эти функции энергосбережения:
на Ubuntu 12.04, вы можете сделать это, добавив их в
GRUB_CMDLINE_LINUX_DEFAULT
вход в/etc/default/grub
и потом работаетupdate-grub
. Параметры загрузки для добавления:intel_idle.max_cstate=0 processor.max_cstate=0 idle=poll
вот кровавые подробности о том, что делают три варианта загрузки:
настройка
intel_idle.max_cstate
до нуля либо вернет драйвер cpuidle вacpi_idle
(по крайней мере, в документации по опции), или отключить его полностью. На моем поле он полностью отключен (т. е. отображение на/sys/devices/system/cpu/cpuidle
производит мощностьnone
). В этом случае второй вариант загрузки,processor.max_cstate=0
это не нужно. Однако в документации указано, что значение max_cstate равно нулю дляintel_idle
драйвер должен вернуть ОС кacpi_idle
драйвер. Поэтому я положил во второй ботинок вариант на всякий случай.The
processor.max_cstate
опция устанавливает максимальное состояние C дляacpi_idle
драйвер до нуля, надеюсь, отключив его. У меня нет системы, на которой я могу это проверить, потому чтоintel_idle.max_cstate=0
полностью выбивает драйвер cpuidle на всех доступных мне аппаратных средствах. Однако, если ваша установка возвращает вас отintel_idle
доacpi_idle
только с первым вариантом загрузки, пожалуйста, дайте мне знать, если второй вариант,processor.max_cstate
сделал то, что было задокументировано делать в комментарии, чтобы я мог обновить этот ответ.наконец, последний из трех параметров,
idle=poll
это настоящая свинья власти. Он отключит C1 / C1E, который удалит последний оставшийся бит задержки за счет гораздо большего потребления энергии, поэтому используйте его только тогда, когда это действительно необходимо. Для большинства это будет излишним, так как задержка C1* не так уж велика. Используя мое тестовое приложение, работающее на оборудовании, которое я описал в исходном вопросе, задержка пошла от 9 нам 3 нам. Это, безусловно, значительное сокращение для приложений с высокой чувствительностью к задержкам (например, финансовая торговля, высокоточная телеметрия/отслеживание, высокая частота). сбор данных и т. д...), но не может стоить понесенного удара электроэнергии для подавляющего большинства настольных приложений. Единственный способ узнать наверняка-это профилировать улучшение производительности вашего приложения по сравнению с фактическим увеличением энергопотребления / тепла вашего оборудования и взвесить компромиссный.обновление:
после дополнительного тестирования с различными
idle=*
параметры, я обнаружил, что параметрidle
доmwait
если поддерживается вашим оборудованием, это гораздо лучшая идея. Кажется, что использованиеMWAIT/MONITOR
инструкции позволяет процессору вводить C1E без какой-либо заметной задержки, добавляемой к времени пробуждения потока. Сidle=mwait
, вы получите более прохладные температуры процессора (по сравнению сidle=poll
), меньше пользы силы и все еще сохраняйте отличные низкие задержки цикла опроса холостого хода. Поэтому мой обновленный рекомендуемый набор параметров загрузки для низкой задержки пробуждения потока процессора на основе этих результатов:intel_idle.max_cstate=0 processor.max_cstate=0 idle=mwait
использование
idle=mwait
вместоidle=poll
может также помочь с инициированием Turbo Boost (помогая процессору оставаться ниже его TDP [тепловой проектной мощности]) и hyperthreading (для которого MWAIT является идеальным механизмом для не потребляя всего физического ядра, в то же время избегая более высокие состояния C). Однако это еще не доказано в тестировании, которое я буду продолжать делать.обновление 2:
The
mwait
опция простоя была удалено из новой версии 3.х ядер (спасибо пользователю ck_ за обновление). Это оставляет нам два варианта:
idle=halt
- должен работать так же какmwait
, но проверьте, чтобы убедиться, что это относится к вашему оборудованию. ЭлементHLT
инструкция почти эквивалентнаMWAIT
с подсказкой состояния 0. Проблема заключается в том, что прерывание требуется для выхода из состояния HLT, в то время как запись в память (или прерывание) может использоваться для выхода из состояния MWAIT. В зависимости от того, что ядро Linux использует в своем цикле ожидания, это может сделать MWAIT потенциально более эффективным. Итак, как я уже сказал, тест/профиль и посмотреть, если он отвечает вашим потребностям латентности...и
idle=poll
- самый высокий вариант представления, за счет силы и жары.
возможно, что стало медленнее, это futex, строительный блок для переменных условий. Это прольет некоторый свет:
strace -r ./test_latency 0 1 &> test_latency_strace & sleep 8 && killall test_latency
затем
for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
который покажет микросекунды, взятые для интересных системных вызовов, отсортированных по времени.
на ядре 2.6.32
$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done futex 1.000140 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000129 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000124 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000119 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000106 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000103 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000102 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 0.000125 futex(0x7f98ce4c0b88, FUTEX_WAKE_PRIVATE, 2147483647) = 0 0.000042 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000038 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000037 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000030 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000029 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 0 0.000028 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000027 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1 0.000018 futex(0x7fff82f0ec3c, FUTEX_WAKE_PRIVATE, 1) = 0 nanosleep 0.000027 nanosleep({1, 0}, {1, 0}) = 0 0.000019 nanosleep({1, 0}, {1, 0}) = 0 0.000019 nanosleep({1, 0}, {1, 0}) = 0 0.000018 nanosleep({1, 0}, {1, 0}) = 0 0.000018 nanosleep({1, 0}, {1, 0}) = 0 0.000018 nanosleep({1, 0}, {1, 0}) = 0 0.000018 nanosleep({1, 0}, 0x7fff82f0eb40) = ? ERESTART_RESTARTBLOCK (To be restarted) 0.000017 nanosleep({1, 0}, {1, 0}) = 0 rt_sig 0.000045 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000040 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000038 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000034 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000033 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000032 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000032 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000028 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000028 rt_sigaction(SIGRT_1, {0x37f8c052b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0 0.000027 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000027 rt_sigaction(SIGRTMIN, {0x37f8c05370, [], SA_RESTORER|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0 0.000027 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000023 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000022 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000019 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
на ядро 3.1.9
$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done futex 1.000129 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000126 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000122 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000115 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000114 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000112 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 1.000109 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 0.000139 futex(0x3f8b8f2fb0, FUTEX_WAKE_PRIVATE, 2147483647) = 0 0.000043 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 0.000041 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 0.000037 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 0.000036 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1 nanosleep 0.000025 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000022 nanosleep({1, 0}, {0, 3925413}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal) 0.000021 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0 rt_sig 0.000045 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000044 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000043 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000040 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000038 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000037 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000035 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000034 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000027 rt_sigaction(SIGRT_1, {0x3f892067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000024 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000023 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 0.000019 rt_sigaction(SIGRTMIN, {0x3f89206720, [], SA_RESTORER|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0
я нашел это 5-летний отчет об ошибке который содержит тест производительности "пинг-понг", который сравнивает
- однопоточный мьютекс libpthread
- переменная условия libpthread
- простые старые сигналы Unix
мне пришлось добавить
#include <stdint.h>
для компиляции, которую я сделал с помощью этой команды
g++ -O3 -o condvar-perf condvar-perf.cpp -lpthread -lrt
на ядре 2.6.32
$ ./condvar-perf 1000000 NPTL mutex elapsed: 29085 us; per iteration: 29 ns / 9.4e-05 context switches. c.v. ping-pong test elapsed: 4771993 us; per iteration: 4771 ns / 4.03 context switches. signal ping-pong test elapsed: 8685423 us; per iteration: 8685 ns / 4.05 context switches.
на ядро 3.1.9
$ ./condvar-perf 1000000 NPTL mutex elapsed: 26811 us; per iteration: 26 ns / 8e-06 context switches. c.v. ping-pong test elapsed: 10930794 us; per iteration: 10930 ns / 4.01 context switches. signal ping-pong test elapsed: 10949670 us; per iteration: 10949 ns / 4.01 context switches.
Я пришел к выводу, что между ядром 2.6.32 и 3.1.9 переключение контекста действительно замедлилось, хотя и не как вы заметили в ядре 3.2. Я понимаю, что это еще не ответ на ваш вопрос, я покопаюсь.
Edit: я обнаружил, что изменение приоритета процесса в реальном времени (оба потока) улучшает производительность на 3.1.9 в соответствии с 2.6.32. Однако установка того же приоритета на 2.6.32 замедляет его работу... пойди разберись-я разберусь с этим подробнее.
вот мои результаты сейчас:
на ядре 2.6.32
$ ./condvar-perf 1000000 NPTL mutex elapsed: 29629 us; per iteration: 29 ns / 0.000418 context switches. c.v. ping-pong test elapsed: 6225637 us; per iteration: 6225 ns / 4.1 context switches. signal ping-pong test elapsed: 5602248 us; per iteration: 5602 ns / 4.09 context switches. $ chrt -f 1 ./condvar-perf 1000000 NPTL mutex elapsed: 29049 us; per iteration: 29 ns / 0.000407 context switches. c.v. ping-pong test elapsed: 16131360 us; per iteration: 16131 ns / 4.29 context switches. signal ping-pong test elapsed: 11817819 us; per iteration: 11817 ns / 4.16 context switches. $
на ядре 3.1.9
$ ./condvar-perf 1000000 NPTL mutex elapsed: 26830 us; per iteration: 26 ns / 5.7e-05 context switches. c.v. ping-pong test elapsed: 12812788 us; per iteration: 12812 ns / 4.01 context switches. signal ping-pong test elapsed: 13126865 us; per iteration: 13126 ns / 4.01 context switches. $ chrt -f 1 ./condvar-perf 1000000 NPTL mutex elapsed: 27025 us; per iteration: 27 ns / 3.7e-05 context switches. c.v. ping-pong test elapsed: 5099885 us; per iteration: 5099 ns / 4 context switches. signal ping-pong test elapsed: 5508227 us; per iteration: 5508 ns / 4 context switches. $