Переключение контекста
Для ОС для переключения контекста между потоками требуется немного времени. Наличие большого количества потоков, каждый из которых выполняет сравнительно небольшую работу, означает, что время переключения контекста начинает составлять значительную часть общего времени выполнения приложения.
Например, для переключения контекста ОС может потребоваться около 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