Я испытываю странное поведение при масштабировании многопроцессорного / многопоточного приложения C ++. Приложение содержит 10 отдельных процессов, взаимодействующих через Доменные сокеты Unix и каждый имеет ~ 100 потоков, выполняющих ввод-вывод, и несколько процессов на этом вводе-выводе. Система является OLTP, и время обработки транзакции имеет решающее значение. IPC IO основан на ускоренной сериализации с использованием zmq поверх сокетов домена unix (это достаточно быстро во всех тестах на нашем локальном сервере, двух старых xeon с 24 ядрами). Теперь мы наблюдаем безумно низкую производительность в системах с большим количеством ядер!
1x Intel® Xeon® X5650 - виртуальный - 6 ядер - TPS ~ 150 (ожидается)
1x Intel® Xeon® E5-4669 v4 - выделенный - 32 ядра - TPS ~ 700 (ожидается)
2x Intel® Xeon® E5-2699 v4 - выделенные - 88 ядер - TPS ~ 90 (должно было быть ~ 2000)
Выполнение нескольких тестов на третьем сервере показывает совершенно нормальную мощность процессора. пропускная способность памяти и латентность выглядят нормально.
htop показывает очень высокое время для ядра (красная часть). Итак, наше первое предположение заключалось в том, что выполнение некоторых системных вызовов занимает слишком много времени или что мы сделали что-то не так в многопоточном коде. (См. Картинку ниже) perf top
сообщает о конкретной процедуре системного вызова / ядра (native_queued_spin_lock_slowpath
), чтобы занять около 40% времени ядра (см. изображение ниже). Мы понятия не имеем, что он делает.
Однако еще одно очень странное наблюдение:
Итак, когда мы запускаем процессы с taskset -cp 0-8 service
мы достигаем ~ 400 TPS.
Как вы можете объяснить, почему уменьшение количества назначенных процессоров с 88 до 8 заставляет систему работать в 5 раз быстрее, но при этом составляет 1/4 ожидаемой производительности на 88 ядрах?
Дополнительная информация:
ОС: Debian 9.0 amd64
Ядро: 4.9.0
Конечно, это похоже на эффект NUMA, когда несколько сокетов резко ухудшают производительность.
perf
очень полезно. Уже в отчете о производительности вы можете увидеть native_queued_spin_lock_slowpath
занимает 35%, что кажется очень большим объемом накладных расходов для вашего кода параллелизма. Сложная часть - это визуализировать, что вызывает, если вы не очень хорошо знаете код параллелизма.
я бы порекомендовал построение графиков пламени из общесистемной выборки ЦП. Быстрый старт:
git clone https://github.com/brendangregg/FlameGraph # or download it from github
cd FlameGraph
perf record -F 99 -a -g -- sleep 60
perf script | ./stackcollapse-perf.pl > out.perf-folded
./flamegraph.pl out.perf-folded > perf-kernel.svg
На получившемся графике найдите самые высокие «плато». Которые указывают функции с самым эксклюзивным временем.
Я с нетерпением жду, когда bpfcc-tools
пакет находится в стабильной версии Debian, он позволит собирать эти «свернутые» стеки напрямую с меньшими накладными расходами.
Что вы с этим сделаете, зависит от того, что вы найдете. Знайте, какая критическая секция защищена замком. Сравните с существующими исследованиями масштабируемой синхронизации на современном оборудовании. Например, в презентации Concurrency Kit отмечается, что разные реализации спин-блокировки имеют разные свойства.
Осмелюсь сказать, что это аппаратная "проблема". Вы перегружаете подсистему ввода-вывода, и это короли, из-за которых больший параллелизм делает ее медленнее (как диски).
Основные показания:
Потому что производители ПО обычно слишком ленивы, чтобы делать многоядерные оптимизации.
Разработчики программного обеспечения редко создают программное обеспечение, которое может использовать все аппаратные возможности системы. Некоторое очень хорошо написанное программное обеспечение можно считать хорошим - это программное обеспечение для майнинга монет, поскольку многие из них могут использовать вычислительную мощность видеокарты, близкую к максимальному уровню (в отличие от игр, которые никогда не приближаются к использованию истинной вычислительной мощности GPU).
То же самое можно сказать и о большом количестве программного обеспечения в наши дни. Они никогда не утруждают себя оптимизацией многоядерных процессоров, поэтому производительность будет лучше, если при запуске этого программного обеспечения будет меньше ядер, настроенных на более высокую скорость, по сравнению с более низкоскоростными ядрами. В случае большего количества и более быстрых ядер это не может быть преимуществом постоянно по той же причине: плохо написанный код. Программа попытается разделить свои подзадачи между слишком большим количеством ядер, и это фактически задержит общую обработку.