Зачем использовать буферы для чтения / записи потоков - PullRequest
9 голосов
/ 12 мая 2010

После прочтения различных вопросов о чтении и написании потоков, все различные ответы определяют что-то вроде этого как правильный способ сделать это:

private void CopyStream(Stream input, Stream output)
{
   byte[] buffer = new byte[16 * 1024];
   int read;
   while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
   {
      output.Write(buffer, 0, read);
   } 
}

Два вопроса:

Зачем читать и писать в этих небольших кусках?

Какое значение имеет размер используемого буфера?

Ответы [ 2 ]

6 голосов
/ 12 мая 2010

Если вы читаете байт за раз, то у каждого вызываемого вами байта есть издержки вызова функции для чтения байта и дополнительные издержки (например, выполнение fileposition += 1 для запоминания места в файле, где вы находитесь, проверка достижения файла и т. д.)

Если вы прочитали 4000 байтов, то у вас будут те же издержки (в приведенном выше примере 1 вызов функции, одно добавление (fileposition + = 4000) и одна проверка, чтобы увидеть, находитесь ли вы в конце файла. Что касается накладных расходов, вы только что сделали это в 4000 раз быстрее (на самом деле, есть и другие затраты, поэтому вы не увидите такого большого выигрыша, но вы резко сократили накладные расходы)

Конечно, вы можете создать буфер размером с весь файл и получить абсолютные минимальные накладные расходы. Однако:

  • файл может быть огромным - больше, чем объем памяти, доступный вашей программе, так что это просто не удастся. Или он может быть настолько большим, что вы начнете использовать виртуальную память, и это резко замедлит процесс. Таким образом, разбивая его на более мелкие фрагменты, вы можете копировать неограниченное количество данных, используя небольшой буфер фиксированного размера

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

  • Вы получаете убывающую отдачу. Чтение 4 байтов вместо 1 может быть в 4 раза быстрее. Но чтение 4000, 40000 или 400000 не ускорит процесс (в действительности, по вышеуказанным причинам большие буферы могут на самом деле замедлить процесс).

  • В некоторых случаях физические устройства работают с определенными размерами данных (например, 4096 байтов на сектор, 128 байтов на строку кэша, 1500 байтов на пакет данных или 8 байтов (64 бита) по шине ЦП). Разделение данных на куски, которые соответствуют (или кратны) базовому механизму транспорта / хранения, может помочь аппаратному обеспечению более эффективно обрабатывать данные.

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

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

4 голосов
/ 12 мая 2010

Обычно вы всегда можете выбрать размер для чтения и записи. Однако некоторые значения будут более оптимальными для конкретных архитектур. Что это, ну, вне моего понимания. Я всегда склонялся к тому, чтобы использовать fgures, с которыми я знаком, например 4K (размер страницы в системах NT, для которых я использовал драйверы записи). Но я экспериментировал в пользовательском режиме с большими размерами, и я никогда не сталкивался с какими-либо проблемами. Я стараюсь, чтобы количество вызовов IO было как можно ниже.

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

Я думаю, что для любого конкретного случая вы должны

  1. Минимизировать количество вызовов IO
  2. Измените эту стратегию, если реальная производительность является проблемой.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...