Статьи

Гиперпоточность: как удвоить производительность процессора?

Автор  Ауримаса Микалаускаса для блога MySQL Performance .

На днях заказчик попросил меня провести планирование емкости для их фермы веб-серверов. Я смотрел на график ЦП для одного из веб-серверов, на котором была включена технология Hyper-Threading, и подумал про себя: «Это должен быть обманчивый график — он показывает 30% загрузки ЦП. Неужели этот сервер не может обрабатывать в 3 раза больше работы? »

Или это может?

Я решил сделать то, что мы обычно делаем в таком случае — я решил проверить это и узнать правду. Оказывается — это больше, чем кажется на первый взгляд.

Как работает Intel Hyper-Threading

Прежде чем мы перейдем к моим результатам тестов, давайте немного поговорим о гиперпоточности. По данным Intel , технология Intel® Hyper-Threading (технология Intel® HT) более эффективно использует ресурсы процессора, позволяя запускать несколько потоков на каждом ядре. В качестве функции производительности технология Intel HT также увеличивает пропускную способность процессора, повышая общую производительность многопоточного программного обеспечения.

Звучит почти как волшебство, но на самом деле (и исправьте меня, если я ошибаюсь), то, что HT делает по сути, — представляя одно ядро ​​ЦП в виде двух ЦП (скорее потоков), это позволяет перенести планирование задач из ядра в ЦП.

Например, если у вас только одно физическое ядро ​​ЦП и две задачи с одинаковым приоритетом, работающие параллельно, ядру придется постоянно переключать контекст, чтобы обе задачи получали достаточное количество процессорного времени. Однако, если у вас есть ЦП, представленный вам как два ЦП, ядро ​​может дать каждой задаче ЦП и взять отпуск.

На аппаратном уровне это все еще будет один ЦП, выполняющий такой же объем работы, но, возможно, будет некоторая оптимизация того, как эта работа будет выполняться.

Моя гипотеза

Вот проблема, которая сводила меня с ума: если HT на самом деле НЕ дает вам в два раза больше энергии, и все же система представляет статистику для каждого потока ЦП отдельно, то при 50% загрузки ЦП (согласно mpstat в Linux) ЦП должен быть максимально увеличен вне.

Поэтому, если я попытался смоделировать масштабируемость этого веб-сервера — 12-ядерная система с поддержкой HT (представленной как 24 ЦП в системе), предполагая идеальную линейную масштабируемость, вот как это должно выглядеть:

Throughput
(requests per second)
  |
9 |         ,+++++++++++++++
  |        +
  |       +
6 |      +
  |     +
  |    +
3 |   +
  |  +
  | +
0 '-----+----+----+----+----
    1   6   12   18   24

В приведенном выше примере один поток ЦП может обработать запрос за 1,2 с, поэтому вы видите, что он максимально работает со скоростью 9-10 запросов / с (12 / 1,2).

С точки зрения пользователя, это ограничение будет ОЧЕНЬ неожиданным, поскольку можно ожидать, что 50% -ое использование будет… ну, именно это — 50% -ое использование.

На самом деле график использования ЦП выглядел бы еще более разочаровывающим. Например, если бы я увеличивал количество параллельных запросов линейно с 1 до 24, вот как должна выглядеть эта взаимосвязь:

CPU utilization:
100% |         ++++++++++++++
     |         .
     |         .
     |         .
     |         .
 50% |         .
     |       +
     |     +
     |   +
     | +
  0% '----+----+----+----+----
    0     6   12   18   24

Следовательно, загрузка ЦП взлетела бы на 12 ядер от 50% до 100%, потому что на самом деле системный ЦП был бы использован на 100% в этот момент.

Что происходит на самом деле

Естественно, я решил провести тест и проверить, верны ли мои предположения. Тест был довольно простым — я написал PHP-скрипт с интенсивным использованием процессора, который потребовал 1,2 с для выполнения на процессоре, который я тестировал, и разбил его на http (apache) с ab при увеличении параллелизма. Вот результат:

Запросов в секунду
Необработанные данные можно найти здесь .

Если это не утомляет вас, пожалуйста, ознакомьтесь с фактами снова и затем вернитесь к графику.

Все еще не уверены, почему я нахожу это интересным? Позволь мне объяснить. Если вы посмотрите внимательно, сначала — при параллелизме от 1 до 8 — он отлично масштабируется. Таким образом, если у вас есть данные только для потоков 1-8 (и вы знали, что процессы не несут задержек когерентности из-за общих структур данных), вы, вероятно, прогнозируете, что они будут линейно масштабироваться до достижения ~ 10 запросов / сек при 12 ядрах. , в этот момент добавление большего количества параллельных запросов не будет иметь никаких преимуществ, так как процессор будет насыщен.

Однако в реальности происходит то, что за последние 8 параллельных потоков (следовательно, за 33% использования виртуального ЦП) время выполнения начинает увеличиваться, а максимальная производительность достигается только при 24-32 одновременных запросах. Похоже, что на отметке 33% происходит какое-то «удушение».

Другими словами, чтобы избежать резкого падения производительности после 50% загрузки ЦП, при 33% использования виртуальных потоков (т.е. 66% фактической загрузки ЦП) система создает иллюзию ограничения производительности — выполнение замедляется, так что система достигает только точка насыщения в 24 потоках (визуально при 100% загрузке процессора).

Естественно, тогда возникает вопрос — имеет ли смысл запускать гиперпоточность в многоядерной системе? Я вижу как минимум два недостатка:

1. Вы не видите реальной картины того, насколько реально используется ваша система — если график ЦП показывает загрузку 30%, ваша система может быть уже загружена на 60%.
2. После 60% физического использования скорость выполнения ваших запросов будет намеренно ограничена для обеспечения более высокой пропускной способности системы.

Так что, если вы оптимизируете для более высокой пропускной способности — это может быть хорошо. Но если вы оптимизируете время отклика, вы можете рассмотреть возможность работы с выключенным HT.

Я что-то пропустил?