Pull to refresh

Comments 29

Тестовый пример запускался под Windows на процессоре Intel Core i7 с 4-мя физическими и с 8 логическими ядрами.

Производительность на 8 потоках увеличилась не в 8, а примерно в 4 раза, но на этом эксперимент можно закончить

всё логично же, 4 физических ядра, получили ×4 к скорости, разница между 4 и 8 потоками минимальная.


Как видно из графиков, при 4-х потоках свободно более 50% процессорных ресурсов

ну да, свободны 4 «виртуальные» ядра (разумеется, если планировщик знает о ht), то есть реальная загрузка близка к 100%. разумеется, ht может дать прирост на некоторых задачах (например, если ядро простаивает из-за промаха кэша, то его «виртуальный двойник» может в это время что-то считать), но в целом можно считать, что после 50-60% загрузки процессора производительность не растёт.

Спасибо за комментарий! Действительно, насчет 8-кратного прироста на 8 логических ядрах я погорячился. Поправлю статью.

Вы уперлись в закон Амдала.)
Вообще когда вы параллелите операции ввода-вывода, http запрос например, то получаете ускорение за счет того что процессор переключает своей выпонение на полезную нагрузку, во время ожидания ответа от переферии. Когда вы выполняете алгоритм состоящий из последовательных операций на процессоре, то вы упираете в закон Амдала. Эти два вида операций имеют сильно разный прирост от параллелизма.

Когда вы выполняете алгоритм состоящий из последовательных операций на процессоре, то вы упираете в закон Амдала.

Я всегда думал, что закон Амдала не имеет вообще никакого отношения к последовательным операциям и гласит, что «команда завершила выступление, когда самый медленный ее участник завершил выступление». Кроме того, в рассматриваемом примере операции никому не обязаны быть последовательными, нужны только чекпоинты, в которых результат из параллельных потоков будет собираться воедино, что почти никак не скажется на итоговом результате.

S = 1 / ('a' + (1 — 'a') / p). В законе доля паралельных операций (1- 'a') делиться на кол-во процессоров p которое ограничено — (1- 'a') / p. Если у вас 8 процесоров то ускорения от увеличения кол-ва параллельных операций вы не получите т.к. p будет постоянным а (1 — 'a') будет расти. Но с ростом кол-ва потоков будет расти 'a' — это управление тредами операционной системой, что автор и получил.

Вообще когда вы параллелите операции ввода-вывода, http запрос например, то получаете ускорение за счет того что процессор переключает своей выпонение на полезную нагрузку


Собственно, об этом я в статье и написал. А начальный пост был о том, что при 4 физических и 8 логических ядрах правильно ожидать прироста производительности кратного количеству физических ядер, то есть в 4 раза. То что он может быть больше и зависит от типа задач, само собой разумеется.
Для идеальности оформления можно результаты в таблицы собрать, будет значительно нагляднее
Согласен, надо будет поправить. При переносе из Google Docs таблицы отъехали и я не стал форматировать.
Утилизация CPU на практике не должна превышать 80% для обеспечения отказоустойчивости.

Это очень спорный тезис нуждается в какой-нибудь аргументации.

В Сбербанке при проведении нагрузочного тестирования одним из критериев его успешности была цифра в 80% утилизации CPU. Запас прочности в 20% кажется вполне обоснованным, особенно с учетом комментария edo1h
В Сбербанке при проведении нагрузочного тестирования одним из критериев его успешности была цифра [...]

Ух. А в деревне Гадюкино при проведении нагрузочного тестирования одним из критериев его успешности была сломанная передняя ось телеги. С каких пор «критерии тестирования в [название любой конторы]» — являются доказательной базой?!


Запас прочности в 20% кажется вполне обоснованным [...]

Кому кажется? И что такое «запас прочности» в данном контексте? Вы из бутылки минералки тоже только четыре пятых выпиваете, а остальное оставляете, чтобы запас прочности не нарушить? Что блин такое страшное произойдет, если я буду использовать ровно столько CPU, за сколько я заплатил, сиречь 100%?


с учетом комментария edo1h

Этот комментарий относился к виртуальным ядрам, и в нем фигурировали цифры 50-60% (тоже нуждающиеся в аргументации, кстати). Каким боком его вообще можно сюда приплести?

Есть логика, которая «предполагает» и есть статистика, которая «располагает». Я полагал, что мы оба понимаем, что утилизация CPU = 100%, это значит, что «сервер встал» и мы спорим лишь о цифре запаса прочности. Если надо обосновать логически почему цифра в 100% загрузки CPU недопустима — пожалуйста. 100% загрузки CPU означает, что мы даем на процессор такую нагрузку, что он не справляется и задачи выстраиваются в очередь, которая постоянно растет. Иными словами, время выполнения каждой задачи (например, запроса пользователя, SQL-запроса в БД) критично увеличивается. И мой практический опыт подтверждает эту логику. Чтоб это доказать нужно сделать пример и провести нагрузочное тестирование. В переписке, как вы понимаете, это невозможно.

С каких пор «критерии тестирования в [название любой конторы]» — являются доказательной базой?!

Доказать эту конкретную цифру можно только статистикой. Я сослался на опыт (статистику) IT специалистов уважаемой компании. Как еще это можно доказать?

Этот комментарий относился к виртуальным ядрам, и в нем фигурировали цифры 50-60% (тоже нуждающиеся в аргументации, кстати). Каким боком его вообще можно сюда приплести?


Речь шла о том, что реального прироста производительности после некоторой цифры утилизации CPU на графике можно не ждать.
Я полагал, что мы оба понимаем, что утилизация CPU = 100%, это значит, что «сервер встал» [...]

С чего бы это? Утилизация ∈ (100, ∞) — да, предполагает. Заметьте, что левая граница интервала здесь открытая, ровно 100 туда не попадает.


100% загрузки CPU означает, что мы даем на процессор такую нагрузку, что он не справляется

И снова нет. ∀ ε > 0 : 100 + ε — да. А ровно 100 — нет.


Утилизация меньше 95% — для нас явный сигнал, что мы что-то делаем неправильно: у нас простаивает мощность, которая могла бы работать и приносить прибыль. У нас, правда, erlang, не java, а там виртуальную машину проектировали люди неглупые, поэтому она в 100% не упрется никогда, а начнет плавно регрессировать.


Но 20% «запаса прочности» — может происходить только от вопиющего непонимания того, как вообще проектируются высоконагруженные системы (и бездонной бочки с баблом, пятую часть из которого можно просто позволить себе выбрасывать на ветер).


Я сослался на опыт (статистику) IT специалистов уважаемой компании.

Ни на опыт, ни на статистику вы не ссылались. Вы сказали, что они высосали из пальца нелепую цифру в 80%.


реального прироста производительности после некоторой цифры утилизации CPU на графике можно не ждать

Пока вы не привели в пример опыт еще какой-нибудь компании, спешу сообщить: у физических (не виртуальных) процессоров прирост вплоть до ста процентов линейный.

И снова нет. ∀ ε > 0: 100 + ε — да. А ровно 100 — нет.


Знать бы еще как обеспечить эти «чуть меньше 100%». Нагрузка обычно неравномерна, поэтому и нужен запас прочности, чтобы «чуть меньше 100%» сейчас не превратились в «чуть больше 100%» через некоторое время. Системы, которые вы разрабатываете, позволяют это обеспечить? Интересно, что это за системы? Какая у них функциональность?

Утилизация меньше 95% — для нас явный сигнал, что мы что-то делаем неправильно


То есть, если на серверах, где работает разрабатываемое вами ПО снять в динамике утилизацию процессора, она будет >95%?

Ни на опыт, ни на статистику вы не ссылались. Вы сказали, что они высосали из пальца нелепую цифру в 80%.


Собственно, спор возник вокруг моего утверждения, что на практике утилизация CPU не должна превышать 80% для обеспечения отказоустойчивости. И я привел пример где такая практика имеет место. Я не говорю, что невозможно построить систему, которая будет отлично работать при стабильной загрузке CPU >95%, но большинство систем, опять же, по моему опыту не такие.
Нагрузка обычно неравномерна, поэтому и нужен запас прочности, чтобы «чуть меньше 100%» сейчас не превратились в «чуть больше 100%» через некоторое время.

Back pressure механизмы люди как раз придумали для выравнивания нагрузки. Нет, если нужно мгновенно обработать внезапный долгий пик с удвоением входного трафика, то все не так радужно (и тут 80% тоже, кстати, не спасут), но есть все-таки целый круг задач, где нагрузка так причудливо не скачет:


  • внутренняя обработка своих данных, без особых лимитов по времени (всякий там ML, анализаторы, проверки целостности, перекладывание сырых логов и т. п.) — тут просто нагружаем процессор, пока не выйдем на 95%
  • обработка входного трафика с пиками, не критичная к мгновенности ответа (обработка входного потока данных с пиками для внутреннего использования) — тут работает back pressure, позволяя накопить очередь в пике и разгрести ее, когда пик просядет
  • обработка трафика с пиками в режиме реального времени — кластер с hashring и back pressure, который в пике запускает новую ноду (или несколько), которые разгребают накопившуюся очередь. Тут много сложностей, и с горячим кэшем, и с мгновенностью старта ноды, и вообще, но они все, как правило, решаемы.

Ну вот, как пример: разгребаем мы поток курсов валют, около 15К в секунду. На каждое вновь прибывшее значение — несколько не вполне легковесных действий. Сначала мы избавились от базы, потому что 15К r/w запросов в секунду плохо вписываются в концепцию контроля производительности. Потом за счет бесплатной вытесняющей многозадачности в виртуальной машине эрланга просто стартовали ≈30К гринтредов, по одному на пару валют. Сделали так, что очередь разгребается в нужном режиме, не накапливаясь. Посмотрели, как оно будет работать, замерили, сколько времени мы проводим в каждом обработчике. Добились того, чтобы mailboxes каждого процесса разгребались чуть быстрее, чем надо, чтобы сообщения не накапливались. Ну и добавили watchdog, который при внеплановом росте нагрузки стартует еще одну ноду в том же кластере / hashring’е.


Ну вот как-то так. Не скажу, что это прям сверхестественно.

Спасибо за развернутые пояснения! Задавая вопрос, я тоже полагал, что вы используете, что-то вроде akka streams в java (реактивные стримы, обратное давление, гринтреды, дросселирование). Сам за эту тему активно топлю, но, к сожалению, реалии пока не позволяют использовать активно эти технологии. Для этого, во-первых, нужно, чтобы все взаимодействующие компоненты поддерживали реактивность, вот, вы говорите, что взяли и избавились от базы — мы пока не можем. Во-вторых, перейти на асинхронное stateless взаимодействие всех микросервисов и компонент тоже достаточно проблематично.

Но, вот, вы пишете:
тут просто нагружаем процессор, пока не выйдем на 95%

А какими средствами вы регулируете нагрузку именно в цифрах утилизации CPU? Понятно, что, скажем, в java/akka есть пулы гринтредов (ForkJoinPool), которые по-умолчанию создают столько физических потоков сколько ядер процессора и таким образом должны оптимально использовать имеющиеся вычислительные ресурсы, но как выйти именно на цифру, скажем, в 95% мне непонятно.
перейти на асинхронное stateless взаимодействие всех микросервисов и компонент тоже достаточно проблематично

Угу. Но оно того сто́ит.


как выйти именно на цифру, скажем, в 95% мне непонятно

Регулировкой back pressure и увеличением нагрузки с ручным мониторингом вплоть до 95%. Мне для моих данных требуются две ноды в кластере: одна захлебывается, три простаивают.


Ручками, ручками регулируем :)) Вы же как-то умеете на 80% выходить.

Этот комментарий относился к виртуальным ядрам,

так уже давно все серверные процессоры (и большинство десктопных, и вроде бы даже некоторые мобильные, не слежу за ними) с smt.
смысла отключать smt при его поддержке процессором тоже давно нет — планировщики что в windows, что в linux «обучены» работать с smt, так что более-менее заметной просадки производительности в случае немногих потоков (что случалось на заре внедрения) не обнаруживается.


и в нем фигурировали цифры 50-60% (тоже нуждающиеся в аргументации, кстати)

а что тут особо аргументировать? если поток не простаивает (ожидание кэшей и тем более промахи кэша, прочие блокировки, промахи предсказателя переходов) и грузит те же блоки (alu, fpu, etc), что и конкуренты, то у ядра нет резерва, который можно перераспределить между потоками.
с примере из этого поста примерно так и получилось, как я понимаю.


ЕМНИП intel оценивает средний прирост от ht примерно в 20%, вот и получаем 50% реальных ядер +20% прироста = 60%. разумеется, это «пальцем в небо».


Что блин такое страшное произойдет, если я буду использовать ровно столько CPU, за сколько я заплатил, сиречь 100%?

ещё один момент, на ксеонах не так давно можно было «зажать» частоту на максимум и он работал так хоть с нагрузкой 100% (максимальную частоту при задейстовании всех ядер можно посмотреть в спеках и часто на wikichip). на gold уже этот номер не прокатывает — при росте нагрузки частота нестабильна, падает ниже заявленной в спеках (упирается в tdp, как я понимаю.
это тоже работает против подхода «гружу как можно больше».


ещё один фактор — много процессов означает много переключений контекста, которые дороги, и т. п.
рост нагрузки выше какого-то уровня (для каждой задачи своего) вызывает падение суммарной произодительности.


и, как говорится, last but not least, во многих задачах важна не только (или не столько) суммарная производительность, но и latency. если, подняв загрузку ядра с 50% до 100%, мы получим прирост производительности на 20%, а рост задержек в 3 раза, то для многих применений мы сделаем хуже (+20% нод стоят не так уж и дорого).
а бывает не в три раза, а на порядок. или, хуже того, растёт число блокировок и deadlock'ов и всё просто встаёт (классический пример — 1с, особенно старые версии с блокировкой всей таблицы на каждый чих, но встречал на разных бд).

если, подняв загрузку ядра с 50% до 100%, мы получим прирост производительности на 20%, а рост задержек в 3 раза

Если мы, все-таки, вернемся к физическим ядрам, то прирост производительности окажется 50%, а роста задержек не будет вовсе при правильно спроектированной виртуальной машине (которая сама разруливает гринтреды и не позволяет потокам неистово биться за контекст).

Если мы, все-таки, вернемся к физическим ядрам, то прирост производительности окажется 50%,

а что не 100%? вы же рядом писали про линейный рост, при нём с загрузки 50% до загрузки 100% производительность вырастет вдвое )


100% загрузки физических ядер с включенным ht и есть 50% загрузки в мониторинге.
а вы рядом написали про 95% загрузки по мониторингу, к которым вы стремитесь. может быть, erlang вызывает дикое количество промахов кэша и неверно предсказанных ветвлений, тогда да, возможен практически линейный рост до 100%. не могу прокоментировать, практически ничего не знаю про erlang.


P. S. мне кажется, вас запутало слово «виртуальный». в данном случае речь не о виртуализации, а о smt, например, о ht у intel, который одно физическое ядро представляет операционной системе как два логических (и это не предел, у ibm есть и smt4, и smt8 — то есть физическое ядро может выглядеть как 8 логических).

Я что-то совсем запутался в процентах.


Я имел в виду 50% от начального, 100% от 50%. Линейный, да.


100% загрузки физических ядер с включенным ht и есть 50% загрузки в мониторинге.

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


Я (как теперь понимаю, возможно ошибочно) всегда считал, что могу на четырех ядрах запустить четыре факториала бесконечности и мониторинг мне покажет ≈100% загрузки. Это не так? Что будет на четырех логических ядрах (да, «логические» привычнее «виртуальных» в этом контексте, спасибо)?


Конкретно эрланг тут, наверное, ни при чем. Или при чем. Потому что я с простой трансформацией данных в десятках тысяч гринтредов наблюдал повторяемый линейный рост до моих 95%, и наивно считал, что обмана тут нет. Он есть? Промахи кэша могут быть обусловлены слишком большим количеством эрланг-процессов?


Где, короче, почитать вместо терзания тут вас вопросами? Спасибо.

В каком именно мониторинге оно покажет 50%?

в любом (top в linux, диспетчер задач в windows).


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


Я (как теперь понимаю, возможно ошибочно) всегда считал, что могу на четырех ядрах запустить четыре факториала бесконечности и мониторинг мне покажет ≈100% загрузки. Это не так? Что будет на четырех логических ядрах (да, «логические» привычнее «виртуальных» в этом контексте, спасибо)?

это как раз так.
вопрос только в том, что 2 факториала уже загрузят эти «псевдо-4» процессора.
факториал (как минимум реализованный циклом) для своего вычисления не требует кучи сложноугадываемых условных перереходов и обращений к памяти, так что alu фактически будет будет загружен на 100%, при этом другие потоки грузят те же блоки alu, так что тут неоткуда взяться заметному выигрышу.


так вот, при двух считающихся факториалах, операционная система будет «думать», что загружены только половина ядер, то есть средняя загрузка 50%. но при этом если увеличивать число потоков, считающих факториал, прироста суммарной производительности мы не увидим (ну или почти не увидим). а с какого-то числа потоков производительность начнёт падать


Потому что я с простой трансформацией данных в десятках тысяч гринтредов наблюдал повторяемый линейный рост до моих 95%, и наивно считал, что обмана тут нет.

а какие процессоры? и чем вы смотрите нагрузку?

вопрос только в том, что 2 факториала уже загрузят эти «псевдо-4» процессора

А. Ага, понял.


а какие процессоры?

AWS large instance


$ lscpu 
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8
On-line CPU(s) list:   0-7
Thread(s) per core:    2
Core(s) per socket:    4
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Stepping:              2
CPU MHz:               2900.054
BogoMIPS:              5800.10
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0-7

чем вы смотрите нагрузку?

Да хоть htop, хоть амазоновой консолью, хоть изнутри эрланга зову :cpu_sup.util/1, результаты не сильно разнятся.

а, ну так у вас виртуализация как раз. тут да, чем больше грузишь, тем больше увезёшь (тем больше ресурсов ты сможешь получить от гипервизора).
я же говорил про baremetal серверы.

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


Нечеловеческое спасибище за ликбез: век живи — век учись. Я когда-то в прошлой жизни работал напрямую с железом, но тогда еще даже турбо-сопроцессоров на 33 MHz не было :) А потом немного отошел — и вон оно как, все нахрен до неузнаваемости.


Пойду потерзаю ноут факториалами vs finger-trees какими-нибудь для сравнения. Спасибо огромное еще раз.

расскажите потом к чему пришли

Я тоже однажды сделал такую глупость. Зачем посылать миллион или миллиард задач, если нужно сделать 500? Пошлите 500.
Sign up to leave a comment.

Articles