Недавно в модуле System.Classes под описанием класса TFileStream я случайно заметил наследуемый от него класс TBufferedFileStream. Судя по документации, TBufferedFileStream добавляет поддержку буферизации в TFileStream, тем самым оптимизируя небольшие последовательные операции чтения/записи файла. Давайте разберемся, что это за зверь, когда и откуда он появился, и, что он дает?
Итак, проведем небольшое расследование. Судя по истории изменения документации (Revision history of "System.Classes.TBufferedFileStream") описание этого класса было добавлено в документацию 15 февраля 2016 года. Т.е. перед выпуском RAD Studio 10.1 Berlin. Согласно "What Was New in Berlin": класс TFDFileStream был перемещен из FireDAC.Stan.Util в System.Classes и переименован в TBufferedFileStream. Ну вот, даже те, кто не пользуются FireDAC, получили пользу от того, что компания Embarcadero купила себе AnyDAC.
Происхождение этого класса мы установили. Теперь давайте посмотрим на сколько он хорош. Напишем две процедуры, которые построчно читают текстовый файл. Первая использует для этого TFileStream:
Тест 1. Файл 1 000 000 строк (173 МБ)
Тест 2. Файл 10 000 000 строк (1,77 ГБ)
Тест 3. Файл 100 000 000 строк (18,6 ГБ)
Как видите использование TBufferedFileStream для последовательного чтения файла дает стабильный прирост производительности. А это значит, он рекомендован к использованию!
Примечания:
Итак, проведем небольшое расследование. Судя по истории изменения документации (Revision history of "System.Classes.TBufferedFileStream") описание этого класса было добавлено в документацию 15 февраля 2016 года. Т.е. перед выпуском RAD Studio 10.1 Berlin. Согласно "What Was New in Berlin": класс TFDFileStream был перемещен из FireDAC.Stan.Util в System.Classes и переименован в TBufferedFileStream. Ну вот, даже те, кто не пользуются FireDAC, получили пользу от того, что компания Embarcadero купила себе AnyDAC.
Происхождение этого класса мы установили. Теперь давайте посмотрим на сколько он хорош. Напишем две процедуры, которые построчно читают текстовый файл. Первая использует для этого TFileStream:
Вторая использует для чтения файла TBufferedFileStream:procedure TestFileStream(const sFileName: String); var fs: TFileStream; sr: TStreamReader; sw: TStopwatch; st: String; begin sw := TStopwatch.StartNew; fs := TFileStream.Create(sFileName, fmOpenRead); try sr := TStreamReader.Create(fs); try while not sr.EndOfStream do st := sr.ReadLine; finally sr.Free end; finally fs.Free; end; sw.Stop; WriteLn(st.Chars[0], ' ', sw.Elapsed.ToString); end;
Когда то давно я реализовывал свой класс для чтения и парсинга CSV-файлов, и он так же как TBufferedFileStream был наследован от TFileStream и так же для ускорения чтения использовал буфер. Похоже это очередное подтверждение теоремы о бесконечных обезьянах... :-) Так вот, когда я тестировал свой класс, максимальной производительности на различных файлах он у меня достигал при буфере размером в 1 МБ. Поэтому во вторую процедуру я добавил параметр "размер буфера" (iBufferSize) и выполню ее с двумя различными размерами буфера - 32 КБ (размер буфера по умолчанию из конструктора класса TBufferedFileStream) и 1 МБ (размер буфера понравившийся мне ранее).procedure TestBufferedFileStream(const sFileName: String; const iBufferSize: Integer); var fs: TBufferedFileStream; sr: TStreamReader; sw: TStopwatch; st: String; begin sw := TStopwatch.StartNew; fs := TBufferedFileStream.Create(sFileName, fmOpenRead, iBufferSize); try sr := TStreamReader.Create(fs); try while not sr.EndOfStream do st := sr.ReadLine; finally sr.Free end; finally fs.Free; end; sw.Stop; WriteLn(st.Chars[0], ' ', sw.Elapsed.ToString); end;
Тест 1. Файл 1 000 000 строк (173 МБ)
Тест | Время выполнения | Прирост производительности, % |
TFileStream | 00:01.9145231 | |
TBufferedFileStream и буфер 32КБ | 00:01.7441268 | 8.90 |
TBufferedFileStream и буфер 1МБ | 00:01.7336918 | 9.45 |
Тест 2. Файл 10 000 000 строк (1,77 ГБ)
Тест | Время выполнения | Прирост производительности, % |
TFileStream | 00:19.7432841 | |
TBufferedFileStream и буфер 32КБ | 00:17.9686645 | 8.99 |
TBufferedFileStream и буфер 1МБ | 00:17.8678814 | 9.50 |
Тест 3. Файл 100 000 000 строк (18,6 ГБ)
Тест | Время выполнения | Прирост производительности, % |
TFileStream | 03:23.0220558 | |
TBufferedFileStream и буфер 32КБ | 03:05.4529768 | 8.65 |
TBufferedFileStream и буфер 1МБ | 03:03.5610330 | 9.59 |
Как видите использование TBufferedFileStream для последовательного чтения файла дает стабильный прирост производительности. А это значит, он рекомендован к использованию!
Примечания:
- Если из файлового потока читать по одному байту, а не как я строками через TStreamReader, то разница в скорости может быть более значительной. Но только кому это надо?
- TBufferedFileStream не дает такого прироста производительности, когда размер буфера меньше, чем читаемая из файлового потока порция данных.
- TBufferedFileStream не дает такого прироста производительности для случайных операций чтения/записи.
procedure TestBufferedFileStream(const sFileName: String;
ОтветитьУдалитьconst iBufferSize: Integer);
var
fs: TBufferedFileStream;
sr: TStreamReader;
sw: TStopwatch;
st: TStringList;
begin
sw := TStopwatch.StartNew;
fs := TBufferedFileStream.Create(sFileName, fmOpenRead, iBufferSize);
try
sr := TStreamReader.Create(fs);
try
st := TStringList.Create;
try
while not sr.EndOfStream do
st.Add(sr.ReadLine);
finally
sr.Free;
end;
finally
fs.Free;
end;
finally
sw.Stop;
WriteLn('Read ', st.Count, ' lines in ', sw.Elapsed.ToString);
st.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TestBufferedFileStream('test.txt', 4096);
end;