Метрика загруженности процессора (CPU utiliztion) — это не то что вы думаете

Submitted by Ромка on Сб, 24/06/2017 - 13:09

Ромка аватар

Всем привет. Предлагаю вашему вниманию свой перевод поста "CPU Utilization is Wrong" из блога Брендана Грегга.

Метрика загруженности процессора (CPU utiliztion), которую все мы привыкли использовать, обычно понимается неправильно. Что такое загруженность процессора? То насколько процессор сейчас занят работой? Нет, это не так, и да, я говорю о метрике %CPU, которая используется всегда и везде, в каждой утилите мониторинга производительности, например в top(1).

Как вы думаете, что значит нагрузка на процессор 90% на картинке ниже?

Вот что это значит на самом деле:

Stalled, то есть "приостановлено" значит, что в данный момент процессор не обрабатывает инструкции, обычно это означает, что он ожидает завершения операций ввода/вывода связанных с памятью (здесь и далее речь о RAM, а не дисковом вводе/выводе). Соотношение между "занято" и "приостановлено" (busy/stalled), которое я привел выше, это то что я обычно вижу в продакшене. Вероятно, что ваш процессор тоже большую часть времени находится в stalled состоянии, но вы об этом и не догадываетесь.

Что это значит для вас? Понимание того насколько много ваш процессор находится в приостановленном состоянии может помочь вам понять куда направить усилия по оптимизации производительности приложения: на ускорение кода или уменьшение числа операций ввода/вывода связанных с памятью. Всем кто заинтересован в оптимизации нагрузки на процессор, в особенности в облаках с настроенным автомасштабированием на основе нагрузки на CPU, будет полезно знать насколько долго процессор находится в приостановленном состоянии.

Что такое нагрузка на процессор на самом деле?

Метрика, которую мы называем нагрузкой на процессор (CPU utilization) на самом деле это "не-idle время", то есть время, которое процессор не выполняет idle-тред. Ядро вашей операционной системы (какую бы ОС вы не использовали) обычно следит за этим во время переключения контекста. Если не-idle тред запустился, а затем спустя 100 милисекунд остановился, то ядро посчитает, что процессор был использован в течение всего этого времени.

Эта метрика так же стара как и системы совместного использования времени (time sharing systems). В бортовом компьютере лунного модуля Apollo (это пионер среди систем совместного использования времени) idle-тред назывался "DUMMY JOB" и инженеры мониторили циклы выполняющие его в сравнении с реальными задачами, это было важной метрикой измерения нагрузки. (Я писал об этом ранее).

Что же с этой метрикой не так?

В наши дни процессоры работают значительно быстрее памяти, поэтому время ожидания памяти доминирует в метрике "нагрузка на процессор". Когда вы видите большие значение %CPU в top(1), вы, должно быть, думаете, что процессор является бутылочным горлышком, когда на самом деле проблема в DRAM.

Со временем все становится только хуже. Долгое время производители процессоров увеличивали тактовые частоты своих процессоров быстрее чем производители памяти уменьшали задержки доступа к памяти (CPU DRAM gap). Примерно в 2005 году процессоры достигли частот в 3 GHz и с тех пор мощность процессоров растет не за счет увеличения тактовой частоты, а за счет большего числа ядер, гипертрединга и многопроцессорных конфигураций. Все это предъявляет еще больше требований к памяти. Производители процессоров пытались снизить задержки связанные с памятью за счет больших по размеру и более умных CPU-кешей, более быстрых шин и соединений. Но проблема со stalled-состоянием все еще не решена.

Как понять, что процессор на самом деле делает

Сделать это можно используя Performance Monitoring Counters (PMC-счетчики): хардверные счетчики, которые могут быть прочитаны с помощью Linux pref (пакет linux-tools-generic в Линуксе) и других утилит. Для примера понаблюдаем за всей системой в течение 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

Ключевая метрика здесь instructions per cycle (insns per cycle: IPC, число инструкций за один цикл), которая показывает сколько в среднем инструкций было выполнено за каждый такт. Чем больше, тем лучше. В примере выше значение 0.78 кажется очень неплохим (нагрузка 78%?) до тех пор пока вы не узнаете, что максимальная скорость процессора это IPC 4.0. Такие процессоры называют 4-wide, это название пошло от особенностей пути извлечения/декодирования инструкций в процессоре (подробнее об этом в Википедии).

Это означает, что процессор может выполнить 4 операции за каждый такт, поэтому значение 0.78 для 4-wide системы означает, что процессор работает на 19,5% от своих возможностей. Новый процессор Skylake от Intel - это 5-wide процессор.

Существуют сотни PMC-счетчиков, которые позволяют детальнее разобраться с производительностью системы, например, посчитать число приостановленных циклов по типам.

В облаках

Если вы работаете в виртуальном окружении, то вероятно у вас нет доступа к PMC-счетчикам, это зависит от поддержки этой фичи гипервизором. Я недавно писал о том, что PMC-счетчики теперь доступны в AWS EC2 в виртуальных машинах базирующихся на Xen.

Как интерпретировать и что делать

Если ваш IPC < 1.0, то вероятнее всего, процессор приостановлен из-за медленной памяти, поэтому нужно оптимизировать софт так, чтобы он требовал меньше операций с памятью, совершенствовать кеширование в процессоре и локальность памяти, особенно в NUMA системах. Оптимизация железа в таком случае подразумевает использование процессоров с большим объемом кешей, более быстрой памятью, шинами и соединениями.

Если ваш IPC > 1.0, то вероятно, вы ограничены числом инструкций, которые может выполнять процессор. Попробуйте найти способ уменьшить число выполняемых инструкций: уменьшить число ненужной работы, кешировать операции и т.п. CPU flame графы — отличная утилита для этих целей. С точки зрения тюнинга железа, попробуйте использовать процессор с большей тактовой частотой и большим числом ядер и гипертредов.

Для моих правил выше я выбрал значение IPC 1.0, почему именно его? Я пришел к нему из своего опыта работы с PMC-счетчиками. Вы можете выбрать для себя другое значение. Сделайте два тестовых приложения, одно упирающееся по производительности в процессор, другое — в память. Посчитайте IPC для них и возьмите среднее значение.

Что инструменты мониторинга производительности должны сообщать вам?

Каждая такая утилита должны показывать IPC вместе с нагрузкой на процессор. Или разделять нагрузку на процессор на instruction-retired и циклы stalled циклы, то есть, %INS и %STL.

Кроме утилиты top(1) для Линукса есть утилита tiptop(1), которая показывает IPC для каждого процесса:

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

Другие причины почему CPU utilization вводит в заблуждение

Проблема со stalled-циклами может быть не только в задержках связанных с памятью:
- изменение температуры может влиять на приостановленность процессора,
- турбобуст может менять тактовую частоту процессора,
- ядро варьирует частоту процессора с определенным шагом,
- проблема с усреднением: 80% нагрузки в течение минуты скроет кратковременный всплеск до 100%,
- спинлоки: процессор нагружен, имеет высокий IPC, но приложение ничего не делает.

Заключение

Нагрузка на процессор (CPU utilization) это обычно неправильно интерпретируемая метрика, так как она включает циклы, потраченные на ожидание ответа от основной памяти, которые могут доминировать в современных нагрузках. Вы можете понять что на самом деле стоит за %CPU используя дополнительные метрики, включая число инструкций за цикл (IPC). Если IPC < 1.0, то вероятно вы упираетесь в память, если IPC > 1.0, то в скорость процессора. Я писал про IPC в своем предыдущем посте, в том числе написал и о использовании PMC-счетчиках, необходимых для измерения IPC.

Инструменты мониторинга производительности, которые показывают %CPU должны показывать PMC-счетчики, чтобы не вводить пользователей в заблуждение. Например, они могут показывать %CPU с IPC и/или число instruction-retired и stalled циклов. Вооруженные этими метриками разработчики и админы могут решить как правильнее тюнинговать их приложения и системы.