libevent/epoll количество рабочих потоков?

Я использую этот пример. В строке №37 указано, что количество рабочих потоков должно быть равно количеству ядер процессора. Почему это так?

Если есть 10 000 подключений, а моя система имеет 8 ядер, означает ли это, что 8 рабочих потоков будут обрабатывать 10 000 подключений? Почему бы мне не увеличить это число?


person user375868    schedule 04.01.2014    source источник
comment
Я думаю, что аргумент может быть таким: если количество потоков соответствует количеству ядер, и каждый поток назначен одному ядру, гарантируется (или должен гарантироваться), что каждый поток работает в лучшем виде; если у вас слишком много потоков, каждое ядро ​​запускает больше потоков, переключаясь между ними; таким образом вы ничего не получите, кроме еще нескольких переключений (что нехорошо).   -  person ShinTakezou    schedule 04.01.2014
comment
так что в любой момент времени только n клиентов (n = # ядер на сервере) получают данные с сервера? это не имеет смысла.   -  person user375868    schedule 04.01.2014
comment
Я просто выдвинул гипотезу о том, почему так написано в строке 39, но я не слишком уверен в этой теме. В любом случае, предоставляя одноядерный ЦП и многозадачную систему с одним ЦП, в любой момент времени работает только ровно один код, поэтому ровно один код (клиент) может получать данные из любого места. Параллелизм — это иллюзия, за исключением случаев, когда у вас несколько процессоров или ядер (и в любом случае это все еще иллюзия, когда ресурс нельзя разделить, а только совместно использовать потоки...). Имеет ли это смысл?   -  person ShinTakezou    schedule 04.01.2014


Ответы (2)


Ваш вопрос даже лучше, чем вы думаете! :-П

Если вы выполняете сетевое взаимодействие с помощью libevent, оно может выполнять неблокирующий ввод-вывод на сокетах. Один поток может сделать это (используя одно ядро), но это приведет к недостаточной загрузке ЦП.

Но если вы выполняете «тяжелый» файловый ввод-вывод, тогда у ядра нет неблокирующего интерфейса. (Многие системы вообще ничего не делают для этого, в других в этой области есть полуфабрикаты, но они не переносимы. — Libevent не использует это.) — Если файловый ввод-вывод является узким местом вашей программы /test, тогда будет иметь смысл большее количество потоков! Если используется жесткий диск, а планировщик ввода-вывода переупорядочивает запросы, чтобы избежать перемещения головок диска и т. д., это будет зависеть от того, сколько запросов планировщик принимает во внимание, чтобы лучше выполнять свою работу. 100 ожидающих запросов могут работать лучше, чем 8.

Почему бы вам не увеличить номер цепочки?

Если выполняется неблокирующий ввод-вывод: все ядра работают с числом потоков = число ядер. Больше потоков означает только большее переключение потоков без усиления.

Для блокировки ввода-вывода: вы должны увеличить его!

person Robert Siemer    schedule 04.01.2014

Переключение контекста

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

Например, для переключения контекста ОС может потребоваться около 10 микросекунд; если поток выполняет работу всего за 15 микросекунд, прежде чем вернуться в спящий режим, то 40% времени выполнения — это просто переключение контекста!

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

Таким образом, в вашем случае, если ваше требование состоит в том, чтобы компьютер обрабатывал 10 000 подключений, а у вас есть 8 ядер, то оптимальная точка эффективности будет составлять 1250 подключений на ядро.

Больше клиентов на поток

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

На сетевом сервере это означает знакомство с системным вызовом select() или epoll(). При вызове они оба переводят поток в спящий режим до тех пор, пока один из упомянутых файловых дескрипторов не станет каким-то образом готовым. Однако, если нет других потоков, беспокоящих ОС во время выполнения, ОС не обязательно будет выполнять переключение контекста; поток может просто сидеть и дремать, пока не будет чем заняться (по крайней мере, это мое понимание того, что делают ОС. Все, поправьте меня, если я ошибаюсь!). Когда некоторые данные появляются от клиента, они могут возобновиться намного быстрее.

И это, конечно, делает исходный код потока намного сложнее. Например, вы не можете блокировать чтение данных от клиентов; сообщение epoll() о том, что файловый дескриптор готов к чтению, не означает, что все данные, которые вы ожидаете получить от клиента, могут быть прочитаны немедленно. И если поток зависает из-за ошибки, затрагивающей более одного клиента. Но это цена, заплаченная за достижение максимально возможной эффективности.

И это не обязательно тот случай, когда вам нужно всего 8 потоков с вашими 8 ядрами и 10 000 соединений. Если есть что-то, что ваш поток должен делать для каждого соединения каждый раз, когда он обрабатывает одно соединение, то это накладные расходы, которые необходимо минимизировать (за счет большего количества потоков и меньшего количества соединений на поток). [Системный вызов select() подобен этому, поэтому был изобретен epoll().] Вы должны сбалансировать эти накладные расходы с накладными расходами на переключение контекста.

10 000 файловых дескрипторов — это много (слишком много?) для одного процесса в Linux, поэтому вам, возможно, придется иметь несколько процессов вместо нескольких потоков. И еще есть небольшой вопрос, способно ли аппаратное обеспечение в принципе поддерживать 10 000 при любых требованиях к времени отклика / подключению, которые есть у вашей системы. Если это не так, вы вынуждены распространять свое приложение на два или более серверов, и это может стать очень сложным!

Точное понимание того, сколько клиентов нужно обрабатывать в каждом потоке, зависит от того, что делает обработка, задействована ли активность жесткого диска и т. д. Таким образом, нет единого ответа; он разный для разных приложений, а также для одного и того же приложения на разных машинах. Настройка клиентов/потоков для достижения максимальной эффективности - действительно сложная работа. Именно здесь инструменты профилирования, такие как dtrace в Solaris, ftrace в Linux (особенно при использовании, как этого), которые я часто использовал в Linux на оборудовании x86) и т. д. могут помочь, потому что они позволяют вам точно понять, какая среда выполнения задействована в вашем потоке, обрабатывающем запрос от клиента.

Такие организации, как Google, конечно же, очень заинтересованы в эффективности; они получают через много электричества. Насколько я понимаю, когда Google выбирает процессор, жесткий диск, память и т. д. для своих известных домашних серверов, они измеряют производительность с точки зрения «поисков на ватт». Очевидно, что вы должны быть довольно крупной компанией, прежде чем станете настолько привередливыми в вещах, но в конечном итоге все так и происходит.

Другая эффективность

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

Разгрузка TCP отражает то, что мы делаем в мире высокоскоростной крупномасштабной встроенной обработки сигналов в реальном времени. (Странные) межсоединения, которые мы используем, требуют нулевого процессорного времени для их запуска. Таким образом, все время процессора предназначено для обработки данных, а специальное оборудование занимается перемещением данных. Это приводит к довольно удивительной эффективности, поэтому можно построить систему с более скромными, более дешевыми и менее энергоемкими процессорами.

Язык также может иметь радикальное влияние на эффективность; Такие вещи, как Ruby, PHP, Perl, очень хороши, но все, кто использовал их изначально, но затем быстро вырос, в конечном итоге перешли на что-то более эффективное, например, на Java/Scala, C++ и т. д.

person bazza    schedule 04.01.2014
comment
Большое спасибо за подробный ответ. - person user375868; 04.01.2014