2011-12-17 2 views
88

Я искал в Интернете некоторые технические подробности о блокировке ввода/вывода и неблокирующих вводах-выводах, и я нашел несколько человек, заявив, что неблокирующий ввод-вывод будет быстрее блокировки ввода-вывода. Например, в this document.Неблокирующий ввод-вывод действительно быстрее, чем многопоточный блокирующий ввод-вывод? Как?

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

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

  1. Чтобы загрузить содержимое файла установить делегатов приложений эту задачу на I/O рамках событийного, передавая функцию обратного вызова наряду с именем файла
  2. Платформа событий делегирует операционную систему, которая программирует контроллер DMA на жестком диске для записи файла непосредственно в память
  3. Структура событий позволяет запускать дополнительный код.
  4. После завершения копирования с диска на память контроллер DMA вызывает прерывание.
  5. Обработчик прерываний операционной системы уведомляет фреймворк ввода-вывода о событиях о том, что файл полностью загружен в память. Как оно это делает? Использование сигнала?
  6. Код, который в настоящее время запускается в рамках фреймворка ввода/вывода.
  7. Событие на основе ввода/вывода рамки проверяет свою очередь и видит сообщение операционной системы, начиная с шага 5 и выполняет обратный вызов он получил на шаге 1.

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

+3

короткий ответ: это больше связано с накладными расходами на наличие резьбы на соединение. non-blocking io позволяет избежать наличия потока на соединение. –

+7

Блокировка IO стоит дорого в системе, где вы не можете создать столько потоков, сколько существует. На JVM вы можете создать несколько тысяч потоков, но что, если у вас более 100 000 соединений? Поэтому вы должны придерживаться асинхронного решения. Однако существуют языки, где потоки не дорогие (например, зеленые потоки), например, в Go/Erlang/Rust, где нет проблем иметь 100 000 потоков. Когда число потоков может быть большим, я считаю, что блокирование ввода-вывода дает более быстрое время ответа. Но это то, что я также должен был бы спросить у экспертов, правда ли это в действительности. – OlliP

+0

@OliverPlow, я тоже так думаю, потому что блокирование ввода-вывода обычно означает, что мы позволяем * системе * обрабатывать «параллельное управление», вместо того чтобы делать это сами, используя очереди задач и т. Д. – Pacerier

ответ

32

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

Давайте посмотрим на возможные реализации программы сетевого сервера, который будет обрабатывать 1000 клиентов, подключенных параллельно:

  1. Один поток для каждого соединения (может быть блокирование ввода/вывода, но может также быть неблокирующая I/О).
    Для каждого потока требуются ресурсы памяти (также память ядра!), Что является недостатком. И каждый дополнительный поток означает больше работы для планировщика.
  2. Один поток для всех соединений.
    Это берет нагрузку от системы, потому что у нас меньше потоков. Но это также мешает вам использовать полную производительность вашего компьютера, потому что вы можете в конечном итоге управлять одним процессором до 100% и позволить всем остальным процессорам бездействовать.
  3. Несколько потоков, в которых каждый поток обрабатывает некоторые соединения.
    Это берет нагрузку от системы, потому что меньше потоков. И он может использовать все доступные процессоры. В Windows этот подход поддерживается Thread Pool API.

Конечно, наличие большего количества потоков не является проблемой. Поскольку вы, возможно, поняли, что я выбрал довольно большое количество соединений/потоков. Я сомневаюсь, что вы увидите какую-либо разницу между тремя возможными реализациями, если мы говорим только о десятках потоков (это также то, что предлагает Раймонд Чен в сообщении блога MSDN Does Windows have a limit of 2000 threads per process?).

В Windows с использованием unbuffered file I/O означает, что запись должна быть размером, кратным размеру страницы. Я не тестировал его, но похоже, что это также может повлиять на производительность записи положительно для буферизованных синхронных и асинхронных записей.

Шаги 1-7, которые вы описываете, дают хорошее представление о том, как это работает. В Windows операционная система сообщит вам о завершении асинхронного ввода-вывода (WriteFile с структурой OVERLAPPED) с использованием события или обратного вызова. Функции обратного вызова вызываются только, например, когда ваш код вызывает WaitForMultipleObjectsEx с bAlertable, установленным на true.

Некоторые более чтение в Интернете:

  • Multiple Threads in the User Interface на MSDN, также в ближайшее время обработки стоимость создания потоков
  • Раздел Threads and Thread Pools говорит: «Хотя потоки относительно легко создавать и использовать, операционная система выделяет значительное количество времени и другие ресурсы для их управления ».
  • CreateThread documentation on MSDN говорит: «Однако ваше приложение будет иметь лучшую производительность, если вы создадите один поток на один процессор и создадите очереди запросов, для которых приложение поддерживает контекстную информацию».
  • Старая статья Why Too Many Threads Hurts Performance, and What to do About It
+0

С точки зрения Интернета общие знания (Интернет, комментарии экспертов) предполагают, что значительно увеличивается макс. количество потоков запросов - это плохая вещь при блокировке ввода-вывода (обработка запросов еще медленнее) из-за увеличения памяти и времени переключения контекста, но не является ли Async IO делать то же самое, когда откладывает задание на другой поток? Да, теперь вы можете подавать больше запросов, но в фоновом режиме должно быть такое же количество потоков. Какова реальная польза от этого? – JavierJ

+1

@JavierJ Вы, кажется, полагаете, что если n потоков выполняет async-файл IO, будут созданы другие n потоков, чтобы сделать файл блокировки IO? Это неправда. ОС имеет поддержку IO-асинхронного ввода-вывода, и ее не нужно блокировать, ожидая завершения ввода-вывода. Он может ставить в очередь запросы ввода-вывода, и если происходит прерывание аппаратного обеспечения (например, DMA), оно может пометить запрос как выполненный и установить событие, которое сигнализирует поток вызывающих. Даже если потребуется дополнительный поток, ОС сможет использовать этот поток для нескольких запросов ввода-вывода из нескольких потоков. –

+0

Спасибо, это имеет смысл, связанный с поддержкой IO OS async-файла, но когда я пишу код для реальной реализации этого (с точки зрения Интернета), скажем, с помощью Java Servlet 3.0 NIO, я все еще вижу поток для запроса и фоновый поток (async), чтобы читать файл, базу данных или что угодно. – JavierJ

0

Улучшение, насколько я знаю, это то, что использует асинхронный ввод-вывод (я говорю о системе MS, просто для уточнения) так called I/O completion ports. При использовании асинхронного вызова структура использует такую ​​архитектуру автоматически, и это должно быть намного более эффективным, чем стандартный механизм потоковой передачи. Как личный опыт, я могу сказать, что вы бы чувствовали, что ваше приложение более реактивное, если вы предпочитаете AsyncCalls вместо блокировки потоков.

2

Одна из возможных реализаций неблокирующего ввода-вывода - это то, что вы сказали, с пулом фоновых потоков, которые блокируют ввод-вывод и уведомляют поток создателя ввода-вывода через некоторый механизм обратного вызова. На самом деле, так работает модуль AIO в glibc. Here - некоторые неясные детали о реализации.

Хотя это хорошее решение, довольно портативное (до тех пор, пока у вас есть потоки), ОС, как правило, может более эффективно обслуживать неблокирующий ввод-вывод. This Wikipedia article перечисляет возможные реализации, кроме пула потоков.

4

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

20

I/O включает в себя несколько видов операций, таких как чтение и запись данных с жестких дисков, доступ к сетевым ресурсам, вызов веб-сервисов или извлечение данных из баз данных. В зависимости от платформы и от типа операции асинхронный ввод-вывод обычно использует преимущества любой аппаратной или низкоуровневой поддержки системы для выполнения операции. Это означает, что он будет выполняться с минимальным воздействием на CPU.

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

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

Таким образом, как указано в [user1629468], асинхронный ввод-вывод не обеспечивает лучшую производительность, а скорее улучшает масштабируемость. Это очевидно при работе в контекстах с ограниченным количеством потоков, например, с веб-приложениями. В веб-приложении обычно используется пул потоков, из которого они назначают потоки каждому запросу. Если запросы заблокированы при длительных операциях ввода-вывода, существует риск истощить веб-пул и заставить замораживать или замедлять веб-приложение.

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

Вы можете прочитать более подробное исследование, которое я недавно сделал по теме асинхронного ввода-вывода и многопоточности here.

+0

Интересно, стоит ли проводить различие между операциями ввода-вывода, которые, как ожидается, будут завершены, и вещами, которые могут быть недоступны (например, «получить следующий символ, который поступает на последовательный порт», в случаях, когда удаленное устройство может или не может отправить что-либо]. Если ожидается, что операция ввода-вывода завершится в течение разумного периода времени, можно отложить очистку соответствующих ресурсов до завершения операции. Однако, если операция может никогда не завершиться, такая задержка будет необоснованной. – supercat

+0

@supercat сценарий, который вы описываете, используется в приложениях и библиотеках нижнего уровня. Серверы полагаются на него, так как они постоянно ждут входящих соединений. Async I/O, как описано выше, не может соответствовать этому сценарию, поскольку он основан на запуске конкретной операции и регистрации обратного вызова для его завершения. В случае, когда вы описываете, вам необходимо зарегистрировать обратный вызов системного события и обработать каждое уведомление. Вы постоянно обрабатываете ввод, а не выполняете операции. Как сказано, это обычно делается на низком уровне, почти никогда в ваших приложениях. –

+0

Шаблон довольно распространен с приложениями, которые поставляются с различными типами оборудования. Серийные порты не так распространены, как раньше, но USB-чипы, которые эмулируют последовательные порты, довольно популярны при разработке специализированного оборудования. Символы от таких вещей обрабатываются на уровне приложения, так как ОС не будет знать, что последовательность входных символов означает, например, открылся кассовый ящик и куда-то нужно было отправить уведомление. – supercat

2

В настоящее время я внедряю async io на встроенную платформу с использованием protothreads. Неблокирующий io делает разницу между скоростью работы от 16000 кадров в секунду до 160 кадров в секунду. Самое большое преимущество non-блокировки io заключается в том, что вы можете структурировать свой код, чтобы делать другие вещи, в то время как оборудование делает свою работу. Даже инициализацию устройств можно выполнять параллельно.

Мартин

2

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

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

Очень легко говорить обо всем этом с молчаливыми предположениями. Например, некоторые из них быстро заявляют, например: «Асинхронный ввод-вывод будет быстрее, чем многопоточный ввод-вывод». Это утверждение сомнительно по нескольким причинам. Во-первых, это может быть случай, когда какая-то заданная асинхронная инфраструктура ввода-вывода реализована точно с многопоточным потоком, и в этом случае они одни и те же, и нет смысла говорить, что одна концепция «быстрее, чем другая» ,

Во-вторых, даже в случае однопоточной реализации асинхронной структуры (такой как однопоточный цикл событий) вы все равно должны делать предположение о том, что делает этот цикл. Например, одна глупая вещь, которую вы можете сделать с однопоточным циклом событий, - это запрос для асинхронного завершения двух разных задач, связанных с ЦПУ. Если вы сделали это на машине с только идеализированным однопроцессорным ядром (игнорируя современную аппаратную оптимизацию), выполнение этой задачи «асинхронно» на самом деле не будет выполняться иначе, чем выполнение с двумя независимо управляемыми потоками или только с одним одиночным процессом - - разница может снизиться до оптимизации контекста потока или оптимизации расписания операционной системы, но если обе задачи будут направлены в CPU, это будет похоже в любом случае.

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

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

Многопоточное исполнение не обязательно должно быть одновременным: вы создаете два потока на машине с одним процессорным ядром или попросите два потока получить любой другой дефицитный ресурс (предположим, скажем, сетевую базу данных, которая может устанавливать только одно соединение за раз). Выполнение потоков может быть interleaved, однако планировщик операционной системы считает нужным, но их общая продолжительность выполнения не может быть уменьшена (и будет увеличена из переключения контекста потока) на одном ядре (или, более общо, если вы создаете больше потоков, чем есть ядра для их запуска или больше потоков, требующих ресурса, чем то, что может поддерживать ресурс). То же самое относится и к многопроцессорной обработке.

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

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

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

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

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

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