Недавно я писал статью - что такое 50% cpu? На системах с hyperthreading 50% cpu по метрикам означает, что большая часть ресурсов сервера уже использована. То есть cpu>50% - это уже "желтая зона", и мы ожидаем замедление всего, чего можно. Но я никогда не думал до экспериментов, что падение производительности может быть столь катастрофическим.

Для экспериментов я использую MSSQL. Если вы не связаны с базами, прочитайте первую часть по диагонали до выводов.

MSSQL: inserts на максималках

Давайте создадим табличку:

create table ins (id int identity primary key, dt datetime, 
  spid int, guid uniqueidentifier, str varchar(100))

и напишем процедуру, которая будет вставлять записи по одной:

create procedure DoIns 
  @n int
as
  set nocount on
  while @n>0 begin
    insert into ins (dt,spid,guid,str) 
	  select getdate(),@@spid,newid(),'this is a string for me and for you'
    set @n=@n-1
	end
GO

Вставка по одной записи является крайне неудобной для SQL server, так как каждая запись вставляется в отдельной транзакции, а они записываются синхронно. На старых HDD, вращающихся дисках вы могли получить скорость вставки в 50-70 записей. К счастью, на моем домашнем SSD получается вставить 6240 записей в секунду, а на тестовом сервере в Яндекс облаке - 15 тысяч. Здесь мы упираемся в latency между сервером и хранилищем и в производительность самого хранилища.

Так как это меня сейчас не интересует я поставлю опцию базы Delayed Durability = FORCED, сделав запись транзакций асинхронной. Скорость вставки сразу возросла до 24500, а на сервере в облаке до 90K в секунду.

Теперь будем вставлять записи во много потоков - в 1,2,3,..8 потоков одновременно. Здесь ограничивающим фактором будет следующее: табличка имеет identity, которые возрастают, следовательно, все потоки вставляют записи в конец, в последнюю страницу. Только один процесс может менять страницу, это критическая секция, которая защищается ожиданием PAGELATCH (не путать с PAGEIOLATCH, которая говорит об ожидании физического ввода вывода).

PAGELATCH довольно редко оказывается узким местом, но если оно оказывается таковым, то Microsoft рекомендует включить специальный флаг для индекса, в данном случае это:

ALTER INDEX PK__ins__3213E83FC16B68B2 ON dbo.ins 
  SET (OPTIMIZE_FOR_SEQUENTIAL_KEY  = ON);

Эксперимент с разным числом потоков

Итак, варьируем число потоков и получаем:

Я не могу объяснить провал на 1 потоке у меня на домашнем компьютере, на сервере в облаке его нет, но в остальном картинка довольно логична: чем больше потоков, там больше они мешают друг другу, насыщая вставку, и опция OPTIMIZE_FOR_SEQUENTIAL_KEY немного помогает.

Пока все логично. Но...

Добавим нагрузку по CPU

Просто тупую расчетную нагрузку, которая никуда не пишет и не читает. Например, такую функцию:

create function [dbo].[isPrimeN] (@n bigint)
returns int
WITH NATIVE_COMPILATION, SCHEMABINDING
as
  BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
  if @n = 1 return 0
  if @n = 2 return 1
  if @n = 3 return 1
  if @n % 2 = 0 return 0
  declare @sq int
  set @sq = sqrt(@n)+1 -- check odds up to sqrt
  declare @dv int = 1
  while @dv < @sq 
    begin
	set @dv=@dv+2
	if @n % @dv = 0 return 0
	end
  return 1
  end
GO

Обращаю внимание, что это natively compiled функция, с обычной трюк может не пройти. Дальше будем просто гонять ее в бесконечном цикле:

declare @n int while 1=1 select @n=dbo.isPrimeN(1000000000000037)

Вставку будем вести 4 потоками. На моем компе 8vcpu = 4cpu HT

Я приведу результаты в виде таблицы:

Число паразитных пожирателей CPU

Скорость inserts / sec

0

74635

1

74502

2

36448

3

33161

4

32894

5

212

6

550

Последние две строки вызывают шок. На сервере в облаке числа были другие, самые плохие были в районе 1500. Но число потоков варьировались: иногда система срывалась в крутое пике на 3 потребителях cpu, иногда на 2, а бывали периоды, когда она тупила так сама по себе. А что еще ожидать от маленького сервера с 8 cpu, который наверняка разделяет хост с большим числом шумных соседей? Именно поэтому и я стал тестировать это на домашней машине, у которой тоже 8 cpu, 4 cpu hyperthreaded.

Анализ

Как такое могло произойти? Итак, что меняется после появления 4 процессов, непрерывно жрущих CPU? Очевидно, задействуются Hyperthreaded половины процессоров (Windows достаточно умна чтобы вначале раскидывать потребителей CPU на cpu 'через один'). Но мы помним, что технология HT дает дополнительные 20-30%. Да, вторые 50% cpu, как я писал в статье, это "ненастоящие" 50%, и мы ожидаем просадки по производительности, но ведь не в 100 раз, правда?

Я попробую поразмышлять. Итак, писатель выставляет заглушку PAGELATCH и входит в критическую секцию, в которой он спокойно модифицирует страницу в BUFFER POOL. По окончании он снимает замок. Процессы, которые уперлись в PAGELATCH уходят ждать (как мне удалось выяснить, это все таки не spinlock а долговременный latch).

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

Дальше все просто - процесс модификации движется очень медленно, критическая секция висит максимально долго, все безумно тормозит. Если заменить natively compiled функцию на обычную, то она будет ходить к scheduler на каждой своей строке и ситуация не доходит до такой крайности. Вполне вероятно, что CPU-intensive процессы вне SQL server точно также тормозили бы его, как тормозят в облаке соседи по хосту. Ничего себе так, сосед по хосту может замедлить ваш процессинг в 100 раз!

Для меня открытием стало то, что в мире HT никто не обещал честного деления ядра. И я построил пример где это разделение максимально нечестно.

Эти размышления могут быть и неверными, мне будет интересно услышать ваше мнение.

Комментарии (20)


  1. poige
    22.11.2024 15:03

    мы помним

    про hyper-threading нужно в основном помнить, что эффект от него очень специфичен для конкретных задач и их комбинации; и бывает, что он ускоряет даже больше, чем на 20–30 %. Поэтому рекомендация обычно крайне простая: не хочется сюрпризов, отключать его. Хочется, но приятных — тестировать.

    Что касается «честного деления», то многое будет зависеть от планировщика. Поэтому запускать «паразитную» нагрузку в том же движке RDBMS, это основной подозрительный момент. Понятно, что так имеет смысл проверять тоже, но прежде, чем говорить за весь hyper-threading, было бы неплохо запустить её «снаружи». И сравнить.


    1. Tzimie Автор
      22.11.2024 15:03

      Попробую попозже, отпишусь


  1. ky0
    22.11.2024 15:03

    Реквестирую следующую часть - "я выдал виртуалкам меньше ядер, и стало не медленнее, а быстрее", в главной роли vsphere cpu scheduler.


    1. Tzimie Автор
      22.11.2024 15:03

      Я знаю этот эффект, работал на VMware. Но я не хозяин Яндекс клауда, они темнилы и я не знаю, с каким недовесом они продают ядра


  1. LeVoN_CCCP
    22.11.2024 15:03

    На выделенных SQL я тоже как-то пришёл к выводу, что HT надо отключать. Но немного с другой логикой - пытая МС насчёт лицензирования (там тоже толком не могли нормально ответить, а гайд написан в максимально далёких от самой системы терминах наподобие Virtual processors - Virtual cores что похоже одно и то же) и распределения нагрузки под 70%+, выяснилось что получаем систему которая начинает подтормаживать, но платим мы как за честные ядра.


  1. sparhawk
    22.11.2024 15:03

    Для чистоты эксперимента не хватает такого же запуска на своем компе с выключенным HT

    Вот если он не покажет такое замедление, то будет удивительно. А если и он замедлит - то просто очередной "HT бесполезен" (но не вреден!!)


    1. Tzimie Автор
      22.11.2024 15:03

      Да, надо сделать мне


  1. kovserg
    22.11.2024 15:03

    Это давно известный факт. Особенно для процессоров intel. Проблема вызывается делением, т.к. блоков целочисленного деления в них мало. Если хотите ускорить вычисления уберите деление из вычислительных потоков которые исполняются на одном ядре или разнесите их на разные ядра, комбинируя с потоками которые не используют целочисленное деление.


  1. speshuric
    22.11.2024 15:03

    SQL Server: "Погоди ты со своими вставками, мне тут надо простоту проверить, я быстро" :)


  1. anoldman25
    22.11.2024 15:03

    Только мое мнение.

    Например, AMD 7 9700x имеет:

    Cache L1:80 KB (per core)

    Cache L2:1 MB (per core)

    Cache L3:32 MB (shared)

    то есть при отключении половины тредов другая половина имеет в два раза больше кэш памяти. То есть оставшиеся половинки более производительные. Конечно, если программа не адаптирована на многопоточность. А такое бывает.


  1. Viacheslav01
    22.11.2024 15:03

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

    Если взять и посмотреть внимательно, что же это такое HT, то можно грубо написать, что планировщики разные, а исполнение одно, и если тупо забить исполнение ненужной работой, то естественно для рабочей нагрузки просто не будет исполнителей.

    Для чистоты эксперемента, отключите HT и сделайте 4 полезных потока и 4 забивающих исполнительные устройства, может тогда вы поймете, что все работает немного не так, как вы представляли.


    1. Viacheslav01
      22.11.2024 15:03

      На самом деле вы как то слабо зашли, надо было уже в асемблер, и нагрузить в цикле вычисления одновременно на пяток интов, 3-4 из них на простое +-, 1-3 на /*, к ним привязать FP на деление, штуки 2-3, все это отполировать avx тоже на 2-3 вычисления. А главное развязать все это дело по регистрам с которыми оно работает. И тогда бы вы познали дзен, когда все стоит колом, хотя вроде оно должно 20-30 процентов в плюс и вообще работать без тормозов.


    1. Jijiki
      22.11.2024 15:03

      взять игры допустим, придётся разбираться

      если анриал то с интерфейсом - придётся разбираться в любом случае в таких азах какие структуры, какие алгоритмы

      если библиотека придётся всё равно разбираться

      интерес и любопытство даёт возможность научиться еще плюс ко всему

      попробуйте например взять высоту террейна - загруженного от карты высот и просто считайте текущую высоту от половины нормали террейна допустим, это иллюстрация простого експеримента которая может упростить решение коллизии например


      1. Viacheslav01
        22.11.2024 15:03

        Проблема в том, что разбираться как раз и нет желания.

        Вот есть аксиома, что HT дает 20-30% к производительности и этого достаточно.

        А если чуть копнуть и понять как оно работает реально, то многое станет на свои места.

        Если брать именно HT то первый раз я поспорил на эту тему году по моему в 2005, и с тех пор, изменилось мало чего, все время от технологии ждут неведомых чудес.


    1. vikarti
      22.11.2024 15:03

      Иногда нужно просто решать задачу :). А потом всплывает что-то неучтенное.

      И внезапно всплывает что как работает HT (или как бывает устроена память в серверах с кучей ядер, особенно если сокетов - хотя бы два и почему NUMA это важно) - стоит все же знать. И хорошо если хотя бы понимаешь что проблема может быть и адекватные источники что почитать.

      В идеале конечно ОС (ну или гипервизор если у нас ОС не на голом железе) должна такие вещи учитывать...и делать хорошо :(


  1. voldemar_d
    22.11.2024 15:03

    А еще в современных процессорах бывают энергоэффективные ядра. И при высокой нагрузке на процессор может оказаться, что нагружены только производительные, а энергоэффективные простаивают. Выглядеть это может так, что программа не успевает делать свою работу, а процессор нагружен на 50%.


    1. konst90
      22.11.2024 15:03

      при высокой нагрузке на процессор может оказаться, что нагружены только производительные, а энергоэффективные простаивают

      Это как раз нормально. А вот плохо будет - когда часть нагрузки раскидывается на эти ядра, а для решения некоторой задачи нужно провести несколько последовательных расчётов. Тогда P-ядра быстро решат свою часть задачи и будут простаивать в ожидании расчёта на E-ядрах. Я с этим в ANSYS влип.

      Причём по умолчанию распределение ядер непрозрачное: запускаешь расчёт на 12 ядрах - он берет не 12 P-ядер, а цепляет E-ядро, и всё.


  1. MaximRV
    22.11.2024 15:03

    Хотелось бы уточнить у Автора, Какая модель процессора использовалась в домашнем сервере.
    Есть различие в реализации SMT (от AMD) и HT (от Intel) и когда-то у SMT была более производительной чем HT.
    Есть пара мыслишек.
    1. Если отключать SMT/HT то потоки, которые исполнялись на одном физическом ядре могут начать обмениваться информацией между собой медленее. У AMD например есть проблема межъядерного взаимодействия между разными CCD. И если потоки "любят" обмениваться информацией между собой, не приведёт ли отключение SMT к ухудшению попадания потоков на один CCD? Понимаю вопрос специфический и вряд ли относится к СУБД.
    2. Если отключать SMT насколько будет влиять "увеличение" кэшей для потока.

    И ещё, насколько я помню у IBM в их процессорах реализована вообще 4х или даже 8ми поточная виртуализация ядер. Или я путаю с Sun Microsystems. Там тоже 8ми поточная была реализована?

    Кроме того, наблюдая как AMD развивает микроархитектуру в Zen 5, складывается ощущение, что они таки хотят увеличить до 4-х многопоточность ядер. Ну и в перспективе конечно увеличить количество физических ядер на один CCD до 16, как это уже реализовано в свежих Zen 5c в процессорах Epic со 192 ядрами.


  1. slonopotamus
    22.11.2024 15:03

    Какой же треш. Зачем-то приплетённая в задачу СУБД, винда, какие-то хранимые процедуры, яндекс-облако, непойми какой проц, отсутствие замеров с собсна отключенным HT, гадание о причинах без каких-либо подтверждений гипотезы...


    1. Tzimie Автор
      22.11.2024 15:03

      Без HT померю. А субд, процедуры и Яндекс облако возникло не просто так, это расследование проблемы