TFileStream метод Read

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

TFileStream метод Read

Сообщение SeZuka » 06.09.2012 20:26:05

Задача открыть файл и проанализировать его. Для кросплатформенности решил использовать TFileStream. Сначала попробовал читать файл побайтно:

Код: Выделить всё
function TMain.Open(const aFileName: TFilename): boolean;
var
  FS: TFileStream;
  b: Byte;
begin
  Result := False;
  // Открытие файла
  FS := TFileStream.Create(aFileName, fmOpenRead + fmShareDenyWrite);
  try
    FS.Seek(0, soFromBeginning);
    while FS.Position < FS.Size do begin
      b := FS.ReadByte;
      // Обработка
    end;
  finally
    FS.Free;
  end;
  Result := True;
end;

В итоге просто побайтное чтение файла размером 30Мб заняло больше часа и это без какой либо обработки!!!

Для ускорения решил сделать через буфер:

Код: Выделить всё
function TMain.Open(const aFileName: TFilename): boolean;
const
  BufSize = 1024 * 512;
var
  FS: TFileStream;
  Buf: array of Byte;
  i, rb: Integer;
  b: Byte;
begin
  Result := False;
  fFN := aFileName;
  // Открытие файла
  FS := TFileStream.Create(aFileName, fmOpenRead + fmShareDenyWrite);
  try
    SetLength(Buf, BufSize);
    FS.Seek(0, soFromBeginning);
    while FS.Position < FS.Size do begin
      rb := FS.Read(Buf, BufSize);
      i := 0;
      while i < rb do begin
        b := Buf[i];
        // Обработка
        Inc(i);
      end;
    end;
  finally
    FS.Free;
  end;
  Result := True;
end;

А тут вообще впадает в бесконечный цикл, так как метод Read возвращает 0 и в буфер ничего не пишется, и соответственно Position не смещается. Как победить???
Lazarus 1.0 x64 Win7 x64
SeZuka
постоялец
 
Сообщения: 209
Зарегистрирован: 05.09.2012 14:58:05

Re: TFileStream метод Read

Сообщение Mr.Smart » 06.09.2012 20:47:12

Смотрим модуль bufstream.
Код: Выделить всё
uses bufstream;

...

var
fs: TFileStream;
buf: TReadBufStream;
begin
  fs:=TFileStream.Create('filename',fmOpenRead);
  buf:=TReadBufStream.Create(fs);
  try
   while buf.Position < buf.Size do begin
      b := buf.ReadByte;
      // Обработка
    end;
  finally
    buf.Free;
    fs.Free;
  end;
Mr.Smart
долгожитель
 
Сообщения: 1796
Зарегистрирован: 29.03.2008 01:01:11
Откуда: из леса!

Re: TFileStream метод Read

Сообщение SeZuka » 07.09.2012 07:40:30

Mr.Smart писал(а):Смотрим модуль bufstream.

Попробовал использовать предложенную вами прокладку, время чтения уменьшилось до пары минут. Подсмотрел как в модуле реализовано чтения в буфер и переделал свою процедуру:

Код: Выделить всё
function TMain.Open(const aFileName: TFilename): boolean;
type
  TBuf = array of Byte;
const
  BufSize = 1024 * 512;
var
  FS: TFileStream;
  BS: TReadBufStream;
  Buf: Pointer;
  i, rb: Integer;
  b: Byte;
begin
  Result := False;
  Buf := nil;
  // Открытие файла
  FS := TFileStream.Create(aFileName, fmOpenRead + fmShareDenyWrite);
  try
    ReAllocMem(Buf, BufSize);
    while FS.Position < FS.Size do begin
      rb := FS.Read(Buf^, BufSize);
      i := 0;
      while i < rb do begin
        b := TBuf(Buf)[i];
        // Обработка
        Inc(i);
      end;
    end;
  finally
    FS.Free;
  end;
  Result := True;
end;

Все заработало, чтение 30Мб полсекунды.
SeZuka
постоялец
 
Сообщения: 209
Зарегистрирован: 05.09.2012 14:58:05

Re: TFileStream метод Read

Сообщение Mr.Smart » 07.09.2012 08:41:13

Эх, велосипедостроители....
Mr.Smart
долгожитель
 
Сообщения: 1796
Зарегистрирован: 29.03.2008 01:01:11
Откуда: из леса!

Re: TFileStream метод Read

Сообщение SeZuka » 07.09.2012 09:04:19

Mr.Smart писал(а):Эх, велосипедостроители....

В лазаре только начинаю работать, до этого был дельфи, и для меня не понятно, почему простые вещи, которые в дельфи работали с полпинка, в лазаре надо допиливать напильником, тратя на это часы драгоценного времени, постоянно лазя в исходники компонентов и читая форумы.
SeZuka
постоялец
 
Сообщения: 209
Зарегистрирован: 05.09.2012 14:58:05

Re: TFileStream метод Read

Сообщение Vapaamies » 07.09.2012 21:42:34

Если по задаче не предполагается обработка данных больше 2 ГБ, самое правильное -- прочитать файл за один раз в строковую переменную, и можно анализировать сколько душе угодно. Не знаю, как во FreePascal, а в Delphi TStrings.LoadFromFile работает именно так.
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 292
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: TFileStream метод Read

Сообщение SeZuka » 08.09.2012 07:19:11

Файл может быть и 100кб и 100мб, так что в память целиком загонять не стоит, иначе бы использовал TMemoryStream.LoadFromFile.
SeZuka
постоялец
 
Сообщения: 209
Зарегистрирован: 05.09.2012 14:58:05

Re: TFileStream метод Read

Сообщение Aleksey Elin » 13.09.2012 22:58:24

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


FPC/Лазарь зедесь совершенно не причем. Ваш код (второй) из первого сообщения будет неверно работать и в дельфи. Дело в том, что вы использовали для буфера динамический массив, который по факту явлеяется указателем, и при чтении функцией FS.Read(Buf, BufSize) вы портите его (указателя) значение в памяти, а заодно и еще 1024 * 512 - SizeOf(Pointer) байт. Результат такого "чтения" непредсказуем.
Надо просто указать куда читать в массив, например так: FS.Read(Buf[0], BufSize).
Aleksey Elin
новенький
 
Сообщения: 15
Зарегистрирован: 13.09.2012 22:34:34

Re: TFileStream метод Read

Сообщение Devil » 14.09.2012 12:01:11

SeZuka

Нужно понимать, почему всё это тормозит.
Работа с файлами - в принципе медленная. Это ОЧЕНЬ узкое место в программе. А у тебя читается каждый байт - это вообще пипец.

Итак, производительность убивает КАЖДОЕ обращение к файлу как элементу файловой системы.
Read, Write, Position, Size, Seek - всё это через функции операционной системы.
Поэтому к примеру если использовать Position и Size как локальные переменные - производительность будет значительно выше.

Теперь чтение!
Это просто ужасно!
Для работы с потоковым чтением файла наиболее правильно буферизовать чтение в стеке (локальной переменной) то ли 16кб, то ли 32кб. Точно не помню. Я использую 32кб. Такая цифра выбрана потому на файловая система на низком уровне использует такой размер для чтения/записи.
Devil
новенький
 
Сообщения: 40
Зарегистрирован: 10.12.2008 09:56:33

Re: TFileStream метод Read

Сообщение Mirage » 15.09.2012 11:07:55

Быстрее всего будет воспользоваться CreateFileMapping.
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Re: TFileStream метод Read

Сообщение Devil » 15.09.2012 15:01:03

Mirage,
есть какие-нибудь тесты для сравнения ?
Devil
новенький
 
Сообщения: 40
Зарегистрирован: 10.12.2008 09:56:33

Re: TFileStream метод Read

Сообщение Aleksey Elin » 15.09.2012 23:18:49

Mirage писал(а):Быстрее всего будет воспользоваться CreateFileMapping.

Devil писал(а):есть какие-нибудь тесты для сравнения ?

На самом деле, все зависит от задачи.
Последовательное чтение файла механизм FileMapping-а вряд ли ускорит, тут достаточно правильного буфферизирования.
В случае же произвольного и заранее непредсказуемого доступа к файлу FileMapping дает существенный выигрыш, особенно при единовременном чтении небольших порций данных из каждого конкретного места. Происходит это, прежде всего, за счет отстутсвия в прикладном коде постоянных проверок и переключений буферов при буфферизированном доступе, которые могут отнимать бОльшую часть времени при чтении данных. Именно поэтому, универсальные базы данных (типа SQL и т.п.) практически всегда используют механизм FileMapping-а.
Бывают и обратные ситуации, когда правильное буфферизирование при произвольном, но предсказуемом доступе эффективнее FileMappinga.
Aleksey Elin
новенький
 
Сообщения: 15
Зарегистрирован: 13.09.2012 22:34:34


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3

Рейтинг@Mail.ru