Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Файловый поток в работе, или как правильно выбирать буфер чтения/ 
:(
    Опции темы
Alexeis
Дата 5.9.2006, 16:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 109
Всего: 459



Файловый поток в работе или как правильно выбирать буфер чтения/записи. 

    В литературе часто можно встретить рекомендации по выбору буфера чтения или записи для получения оптимальной производительности. Однако нельзя сказать, что в этом вопросе достигнута полная ясность. Для того чтоб немного лучше в этом разобраться я провел эксперименты  с буферами различной длинны. В частности исследовалась работа со стандартным дельфийским файловым потоком TFileStream.

   Первая часть краткий обзор самых важных методов TFileStream (те кто знают смело пропускают)
1) Привожу для новичков, которые до сих пор боятся ими пользоватся.
  -constructor Create(const FileName: string; Mode: Word);
FileName – путь к файлу
Mode – тип файловой операции
  • fmCreate – создаем новый файл
  • fmOpenRead – открываем существующий для чтения
  • fmOpenWrite – открываем существующий для записи
  • fmOpenReadWrite – открываем для чтения и записи одновременно
    Кроме того, может комбинироваться с другими флагами определяющими разрешение на одновременный доступ к этому файлу других процессов или потоков (см. справку)

-function Read(var Buffer; Count: Integer): Integer; - читает Count байт из потока начиная с текущей позиции в файле в переменную Buffer и возвращает число реально скопированных байт. Внимание длинные строки (ansistring, string, widestring и т.д.) так же как и динамические массивы являются указателями, поэтому в функцию передают обычно первый элемент массива (индекс “0”) или сторки (“1”) при этом установка правильной длинны массива (строки) должна быть произведена предварительно. Типичной ошибкой также является попытка чтения, когда указатель позиции (position) находится в конце файла. Указатель позиции автоматически сдвигается, если до того проводились любые стандартные операции записи в поток.

-function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
устанавливает текущую позицию – Offset  в файле относительно начала потока (файла), текущей позиции и конца файла. Это определяется значением параметра
 Origin:
  • soFromBeginning – начало потока (файла)
  • soFromCurrent – текущая позиция
  • soFromEnd – конец файла
Seek(17, soFromBeginning); - тоже самое, что 
position := 17;

  -function Write(var Buffer; Count: Integer): Integer;
Пишет Count байт из переменной в поток и возвращает число реально скопированных байт. Внимание: ни Read ни Write не контролируют реальный размер переданной переменной Buffer, а потому если указать значение Count больше настоящего, то в лучшем случае дополнительно запишутся (считаются) байты соседние переменные (произвольную область памяти), в худшем вы попадете в запрещенную для записи (чтения) область памяти и вызовите ошибку записи (чтения).

  -property Size: Int64 read GetSize write SetSize64;
Указывает размер фала. Если файл создан нами (в конструкторе указали Mode = fmCreate), то желательно сразу же указать предполагаемый размер файла это ускорит операции записи за счет того что не надо будет его наращивать при каждой операции записи. 

  -function CopyFrom(Source: TStream, Count: Int64): Int64;
пишет данные из любого другого потока в наш, начиная с текущей позиции в потоке-источнике. Если Count = 0, то CopyFrom сделает полную копию потока-источника в файле.  У этой функции есть серьезный недостаток, она начинает писать в наш поток начиная с нулевой позиции в нашем потоке, тем самым не позволяя производить добавление (наращивание файла)

Ну вот и весь минимальный набор.
-----------------------------------------------------------------------------

2) Собственно эксперимент. Для тех,  кто захочет повторить мой эксперимент сразу скажу, что это не так просто как кажется на первый взгляд, по одной простой причине, что для сравнения требуются многочисленные повторения в одинаковых условиях, а windows обладает фантастической способностью кэшировать повторяющиеся операции, причем на столько, что изменение размера буфера на число, полученное возведением двойки в степень кратную 10 или даже произведением целого числа на вышеупомянутое число (во завернул) приводит к тому, что скорость чтения (записи) неожиданно растет в разы!
Для преодоления такого кэширования я создал три файла по 128мб каждый и чередовал их для повторных операций. Буфер удобно менять на определенное число процентов от исходного. 

Параметры системы
Винчестер Samsung  SV0411N 40 Гб
Процессор Intel Pentium III 800МГц. Слабоват конечно, но на суть эксперимента это не влияет.

-    чтение из фала в память
user posted image
Далее привожу примерный графики загрузки процессора (снятые на глаз). 
user posted image

   А теперь проанализируем. 
  Удивительно, но мои предположения о том, что максимальная скорость чтения будет при размере буфера равном внутреннему буферу винчестера, т.е. 1-8мб с треском провалились. Максимальная скорость чтения стабильно наблюдается в диапазоне от 500 байт до 50кб. Однако если посмотреть на график загруженности процессора, то видно, что при значениях от 500 до 5кб процессор сильно загружен, а от 10 до 50 загружен намного меньше. 
  Дело в том что помимо чистой пересылки данных за один вызов Read осуществляется еще много других “лишних” вычислений. Т.о. чем меньше буфер тем больше надо производить вызовов Read. Винчестер тем не менее производит чтение намного реже поскольку Window об этом беспокоится и накапливает данные перед очередной пересылкой, в связи с этим скорость чтения почти не меняется, но вот процессор из-за слишком частого вызова Read начинает не успевать пересылать данные, поскольку занят большую часть времени не самой пересылкой, а обработкой многочисленных вызовов и системных проверок и в конце концов при размере буфера порядка нескольких байт просто не успевает за винчестером. Это и есть самый плохой режим чтения.
   А как объяснить сильный спад после 50кб, я затрудняюсь ответить, возможно это результат не синхронности операций чтения и из внутреннего буфера в переданный нами. Косвенно, на это указывает, и то, что скорость чтения начитает опять расти после того как переданный буфер становится многократно больше внутреннего. Но вот одна беда операции чтения имеют высокий приоритет среди прочих, а потому использование огромного буфера для чтения из файла приведет к тому, что все программы просто перестанут отвечать события. 
  Отсюда видится, что золотой серединой является буфер от 10 до 40кб. При больших внутренних кэшах винчестеров правый предел может быть и больше. 



user posted image

  Он выглядит намного более ровным. Но имеется особенность справа. К сожалению  этот эффект связан с эффектом повторного кэширования и при однократном чтении не наблюдается. 
  Слева идет спад, почему он происходит я думаю, очевидно, из предыдущего рассмотрения. Операция записи происходит примерно в 2,5 раза медленнее чтения, и почти не зависит от размера буфера. Видимо этот фактор является самым сильным, а потому остальные почти не заметны на его фоне. Для записи имеет смысл подбирать буфер не менее 10кб, но не более 10мб, по тем же соображениям что ранее были высказаны.

А вот и самое интересное. Копирование. 

user posted image

И график загруженности процессора

user posted image

 Эта операция интересна еще тем, что определяет работу связанную с операциями чтения и записи из произвольных областей. Посмотрим на график.
   Если не обращать внимание на его изрезанность, то видно, что он монотонно растет. Казалось бы странно, учитывая как себя ведут два предыдущих два графика. Но в данном случае определяющим фактором является уже не скорость записи (она определяет только максимальную скорость чтения см. справа) а скорость перемещения считывающих головок. К сожалению Window тут совсем не помогает. Операции копирования он очень плохо кэширует, а потому эффективность операции сильно зависит от программиста. 
   При размере буфера порядка 50кб! Скорость чтения копирования становится чрезвычайно низкой, при этом как видно из графика загруженности процессора, он тоже здесь ни при чем (загрузка минимальная). Это самый плохой режим, который вдобавок приводит к износу механики. Чем больше размер буфера, тем реже происходит переключение головок с одной позиции на другую.  
   Если посмотреть на график справа, то он сильно изрезан. Это, вероятно, происходит от того что, программа не одна обращается к винчестеру, число собственных переключений уже мало, а потому на общее число переключений (в процентном соотношении) начинает сильно влиять прерывания на чтение/запись других программ. 
   От 2 до 12 мегабайт наблюдается пологий мало изрезанный участок, где к тому же достаточно велика загруженность процессора - это говорит о высокой эффективности передачи данных, поскольку доля “лишних” вычислений для таких буферов уже мала. 
  Кстати программа TotalCommander в экспертном режиме, для копирования с логического диска на логический использует 10-ти мегабайтный буфер, что как раз попадает в указанный интервал.        Аналогичный размер буфера логично использовать, если производятся несколько операций чтения или записи одновременно, т.е. любые операции связанные с частым перемещением головок винчестера.

   В заключение хочется отменить, что с аналогичными проблемами мы встречаемся при инсталляции и копировании программ имеющих большое количество ресурсов или других мелких файлов. При их копировании просто не возможно использовать большой буфер копирования, поскольку стандартными средствами нельзя за раз скопировать более одного файла, что зачастую приводит к тому что время копирования такой программы будет многократно больше чем если бы она состояла из одного большого файла. Хочется в пример привести игры Warcraft3 и Starctaft (других не знаю), у которых “весь вес” сосредоточен в нескольких больших архивах это очень дружественный ход по отношению к пользователю, кроме того, в этом случае можно производить загрузку в память многих мелких ресурсов(500 байт – 10кб) за раз, что при грамотном проектировании позволит значительно ускорить ее загрузку за счет использования оптимального буфера чтения.
У меня все!


Это сообщение отредактировал(а) alexeis1 - 5.9.2006, 16:35


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1002 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.