- PVSM.RU - https://www.pvsm.ru -
Та метрика, которую мы называем «загрузкой процессора» на самом деле многими людьми понимается не совсем верно. Что же такое «загрузка процессора»? Это то, насколько занят наш процессор? Нет, это не так. Да-да, я говорю о той самой классической загрузке CPU, которую показывают все утилиты анализа производительности — от диспетчера задач Windows до команды top в Linux.
Вот что может означать «процессор загружен сейчас на 90%»? Возможно, вы думаете, что это выглядит как-то так:
А на самом деле это выглядит вот так:
«Работа вхолостую» означает, что процессор способен выполнить некоторые инструкции, но не делает этого, поскольку ожидает чего-то — например, ввода-вывода данных из оперативной памяти. Процентное соотношение реальной и «холостой» работы на рисунке выше — это то, что я вижу изо дня в день в работе реальных приложений на реальных серверах. Есть существенная вероятность, что и ваша программа проводит своё время примерно так же, а вы об этом и не знаете.
Что это означает для вас? Понимание того, какое количество времени процессор действительно выполняет некоторые операции, а какое — лишь ожидает данные, иногда даёт возможность изменить ваш код, уменьшив обмен данных с оперативной памятью. Это особенно актуально в нынешних реалиях облачных платформ, где политики автоматического масштабирования иногда напрямую завязаны на загрузку CPU, а значит каждый лишний такт «холостой» работы стоит нам вполне реальных денег.
Та метрика, которую мы называем «загрузкой процессора» на самом деле означает нечто вроде «время не-простоя»: то есть это то количество времени, которое процессор провёл во всех потоках кроме специального «Idle»-потока. Ядро вашей операционной системы (какой бы она ни была) измеряет это количество времени при переключениях контекста между потоками исполнения. Если произошло переключение потока выполнения команд на не-idle поток, который проработал 100 милисекунд, то ядро операционки считает это время, как время, потраченное CPU на выполнение реальной работы в данном потоке.
Эта метрика впервые появилась в таком виде одновременно с появлением операционных систем с разделением времени. Руководство программиста для компьютера в лунном модуле корабля «Апполон» (передовая на тот момент система с разделением времени) называла свой idle-поток специальным именем «DUMMY JOB» и инженеры сравнивали количество команд, выполняемых этим потоком с количеством команд, выполняемых рабочими потоками — это давало им понимание загрузки процессора.
Так что в этом подходе плохого?
Сегодня процессоры стали значительно быстрее, чем оперативная память, а ожидание данных стало занимать львиную долю того времени, которое мы привыкли называть «временем работы CPU». Когда вы видите высокий процент использования CPU в выводе команды top, то можете решить, что узким местом является процессор (железка на материнской плате под радиатором и кулером), хотя на самом деле это будет совсем другое устройство — банки оперативной памяти.
Ситуация даже ухудшается со временем. Долгое время производителям процессоров удавалось наращивать скорость их ядер быстрее, чем производители памяти увеличивали скорость доступа к ней и уменьшали задержки. Где-то в 2005-ом году на рынке появились процессоры с частотой 3 Гц и производители сконцентрировались на увеличении количества ядер, гипертрейдинге, много-сокетных конфигурациях — и всё это поставило ещё большие требования по скорости обмена данных! Производители процессоров попробовали как-то решить проблему увеличением размера процессорных кэшей, более быстрыми шинами и т.д. Это, конечно, немного помогло, но не переломило ситуацию кардинально. Мы уже ждём память большую часть времени «загрузки процессора» и ситуация лишь ухудшается.
Используя аппаратные счетчики производительности. В Linux они могут быть прочитаны с помощью perf [1] и других аналогичных инструментов. Вот, например, замер производительности всей системы в течении 10 секунд:
# perf stat -a -- sleep 10
Performance counter stats for 'system wide':
641398.723351 task-clock (msec) # 64.116 CPUs utilized (100.00%)
379,651 context-switches # 0.592 K/sec (100.00%)
51,546 cpu-migrations # 0.080 K/sec (100.00%)
13,423,039 page-faults # 0.021 M/sec
1,433,972,173,374 cycles # 2.236 GHz (75.02%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
1,118,336,816,068 instructions # 0.78 insns per cycle (75.01%)
249,644,142,804 branches # 389.218 M/sec (75.01%)
7,791,449,769 branch-misses # 3.12% of all branches (75.01%)
10.003794539 seconds time elapsed
Ключевая метрика здесь это "количество инструкций за такт" (insns per cycle: IPC), которое показывает, сколько инструкций в среднем выполнил процессор на каждый свой такт. Упрощённо: чем больше это число, тем лучше. В примере выше это число равно 0.78, что, на первый взгляд кажется не таким уж плохим результатом (78% времени выполнялась полезная работа?). Но нет, на этом процессоре максимально возможным значением IPC могло бы быть 4.0 (это связано со способом получения и выполнения инструкций современными процессорами). То есть наше значение IPC (равное 0.78) составляет всего 19.5% от максимально возможной скорости выполнения инструкций. А в процессорах Intel начиная со Skylake максимальное значение IPC уже равно 5.0.
Когда вы работаете в виртуальном окружении, то можете и не иметь доступа к реальным счетчикам производительности (это зависит от используемого гипервизора и его настроек). Вот статья о том, как это работает в Amazon EC2 [2].
Если у вас IPC < 1.0, то я вас поздравляю, ваше приложение простаивает в ожидании данных от оперативной памяти. Вашей стратегией оптимизации производительности в данном случае будет не уменьшение количества инструкций в коде, а уменьшение количества обращений к оперативной памяти, более активное использование кэшей, особенно на NUMA-системах. С аппаратной точки зрения (если вы можете на это влиять) будет разумным выбрать процессоры с большими размерами кэшей, более быструю память и шину.
Если у вас IPC > 1.0, то ваше приложение страдает не столько от ожидания данных, сколько от чрезмерного количества выполняемых инструкций. Ищите более эффективные алгоритмы, не делайте ненужной работы, кэшируйте результаты повторяемых операций. Применение инструментов построения и анализа Flame Graphs [3] может быть отличным способом разобраться в ситуации. С аппаратной точки зрения вы можете использовать более быстрые процессоры и увеличить количество ядер.
Как вы видите, я провёл черту по значению IPC равному 1.0. Откуда я взял это число? Я рассчитал его для своей платформы, а вы, если не доверяете моей оценке, можете рассчитать его для своей. Для этого напишите два приложения: одно должно загружать процессор на 100% потоком выполнения инструкций (без активного обращения к большим блокам оперативной памяти), а второе должно наоборот активно манипулировать данным в ОЗУ, избегая тяжелых вычислений. Замерьте IPC для каждого из них и возьмите среднее. Это и будет примерная переломная точка для вашей архитектуры.
Я считаю, что каждый инструмент мониторинга производительности должен показывать значение IPC рядом с загрузкой процессора. Это сделано, например, в инструменте tiptop под Linux:
tiptop - [root]
Tasks: 96 total, 3 displayed screen 0: default
PID [ %CPU] %SYS P Mcycle Minstr IPC %MISS %BMIS %BUS COMMAND
3897 35.3 28.5 4 274.06 178.23 0.65 0.06 0.00 0.0 java
1319+ 5.5 2.6 6 87.32 125.55 1.44 0.34 0.26 0.0 nm-applet
900 0.9 0.0 6 25.91 55.55 2.14 0.12 0.21 0.0 dbus-daemo
Процессор может выполнять свою работу медленнее не только из-за потерь времени на ожидание данных из ОЗУ. Другими факторами могут быть:
Загрузка процессора стала сегодня существенно недопонимаемой метрикой: она включает в себя время ожидания данных от ОЗУ, что может занимать даже больше времени, чем выполнение реальных команд. Вы можете определить реальную загрузку процессора с помощью дополнительных метрик, таких, как количество инструкций на такт (IPC). Значения меньшие, чем 1.0 говорят о том, что вы упираетесь в скорость обмена данными с памятью, а большие — свидетельствуют о большой загруженности процессора потоком инструкций. Инструменты замера производительности должны быть улучшены для отображения IPC (или чего-то аналогичного) непосредственно рядом с загрузкой процессора, что даст пользователю полное понимание ситуации. Имея все эти данные, разработчики могут предпринять некоторые меры по оптимизации своего кода именно в тех аспектах, где это принесёт наибольшую пользу.
Автор: Инфопульс Украина
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vy-sokaya-proizvoditel-nost/255867
Ссылки в тексте:
[1] perf: http://www.brendangregg.com/perf.html
[2] Amazon EC2: http://www.brendangregg.com/blog/2017-05-04/the-pmcs-of-ec2.html
[3] Flame Graphs: http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
[4] Источник: https://habrahabr.ru/post/329206/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.