Всем привет! Сегодня я расскажу вам, как можно читать файлы большого объема (до 3 ГБ) очень быстро, особо не напрягая при этом систему. Вступление Бывает, когда файлы нужно читать байт за байтом (поиск каких-то сигнатур с пропуском каких-то блоков, например). Сложилось так, что обычная операция чтения через файловый поток читает за раз не один байт, а несколько (точно не помню сколько). И, выходит, что при чтении одного байта мы совершаем кучу лишних телодвижений: Прочитать блок размером 4096 КБ, например; Выделить из него один байт; Скопировать этот байт в другой участок памяти для сравнения и т.д. Это существенно замедляет скорость работы с файлами. Memory Mapped Files позволяют избежать лишних извращений и напрямую читать нужные данные. Memory Mapped Files Memory Mapped Files - это такая крутая штука, придуманная фирмой мелкософт для минимизации количества операций чтения/записи в память при работе с файлами на жестком диске. Вот небольшая цитата с Педивикии: ...и мне вот тоже ничего сначала не понятно было. А потом оказалось, что все куда легче и понятнее (особенно на практике). Вот и приступим... Приготовления Перед тем, как начать, убедитесь, что у вас установлена Delphi (можно любая версия, но я буду использовать Delphi 7). Теперь нам нужно скачать библиотеку KOL (это ссылка) (и дополнение к ней - KOLAdd). Все это дело распаковываем куда-нибудь в одну папку, и прописываем путь к этому каталогу здесь: Tools->Environment Options...->Вкладка Library->Первая кнопка с многоточием (...)->В поле для ввода пишем путь к каталогу->Жмем Add->OK. Теперь кодинг. Кодим тест-сравнение Открываем Delphi 7, создаем консольный проект (File->New->Other...>Console Application). Мы будем сравнивать обычное чтение через PStream и через Mapped Files. Файл будет размером 400 МБ (например, видеоролик, или маленький фильм). Подготовка Перед тем, как начать использовать KOLAdd (в нем содержатся функции для работы с Memory Mapped Files), нам нужно его немного подредактировать: убрать все лишнее, и немного подредактировать код (данный модуль писался давно, поэтому требования немного изменились). Найдем в файле KOLAdd.pas код функции MapFileRead (собственно, нам понадобится только две функции: эта, да еще UnmapFile, поэтому можно спокойно удалить все остальные - я так и сделаю). Конечный файлик получился вот с таким кодом: Код: unit KOLadd; interface uses Windows, KOL; function MapFileRead( const Filename: AnsiString; var hFile, hMap: THandle ): Pointer; procedure UnmapFile( BasePtr: Pointer; hFile, hMap: THandle ); implementation function MapFileRead( const Filename: AnsiString; var hFile, hMap: THandle ): Pointer; var Sz, Hi: DWORD; begin Result := nil; hFile := FileCreate( KOLString(Filename), ofOpenRead or ofOpenExisting or ofShareDenyNone ); hMap := 0; if hFile = INVALID_HANDLE_VALUE then Exit; Sz := GetFileSize( hFile, @ Hi ); hMap := CreateFileMapping( hFile, nil, PAGE_READONLY, Hi, Sz, nil ); if hMap = 0 then Exit; //if (Hi <> 0) or (Sz > $0FFFFFFF) then Sz := $0FFFFFFF; //Здесь я убрал уменьшение до 256 МБ объема проецируемого на память файла Result := MapViewOfFile( hMap, FILE_MAP_READ, 0, 0, Sz ); end; procedure UnmapFile( BasePtr: Pointer; hFile, hMap: THandle ); begin if BasePtr <> nil then UnmapViewOfFile( BasePtr ); if hMap <> 0 then CloseHandle( hMap ); if hFile <> INVALID_HANDLE_VALUE then CloseHandle( hFile ); end; end. Собственно код Давайте сначала определимся, что мы будем делать с этим файлом. Предлагаю посчитать общую сумму всех байт файла, но от результата мы возьмем только младший байт. Делается это простым приведением результата к типу Byte. Код: a := $AABBCCDD; b := byte(a); Я приведу полный код со своими комментариями и результатом, который я получил (в виде скриншота). Код: program memmapfil; {$APPTYPE CONSOLE} uses KOL, KOLAdd, windows; var sum, buf: byte; pFile, pStart: PByte; hFile, hMap: THandle; stream: PStream; time, i, Filesz: Cardinal; begin //Узнаем размер файла //ParamStr(1) - переданный через параметр // командной строки путь к файлу Filesz := FileSize(ParamStr(1)); //Откроем файл на чтение stream := NewReadFileStream(ParamStr(1)); //До операций чтения узнаем время начала time := GetTickCount; for i:= 1 to Filesz do begin stream.Read(buf,1); sum := byte(sum + buf); end; //И выведем итого время, затраченное на чтение Writeln('Elapsed time by PStream: ',GetTickCount-time); Writeln(sum); //Закроем файл stream.Free; sum := 0; time := GetTickCount; //Создаем Mapped File на наш файл размером 400 МБ pFile := MapFileRead(ParamStr(1),hFile,hMap); //Запоминаем начало файла для последующего закрытия этого файла pStart := pFile; for i:= 1 to Filesz do begin //pFile^ - это текущий читаемый байт файла //Т.к. pFile - это указатель, то, чтобы читать //байты, нужно получать текущее значение //так называемым "разыменованием" указателя, //указав после указателя значок ^ sum := byte(sum + pFile^); Inc(pFile); end; //Закрываем файл UnmapFile(pStart, hFile, hMap); Writeln('Elapsed time by MMF: ',GetTickCount-time); Writeln(sum); end. Результаты: Цифры тут указаны в миллисекундах. Как видим, разница просто огромная. Результат мы получили одинаковый, что говорит о верности обоих методов. Важное замечание При работе с MMF нужно следить, чтобы читаемый по указателю байт (через ^) не попадал за пределы файла, иначе будет срабатывать исключение Access Violation. Следить за этим просто. Нужно всего лишь знать размер файла, и воспользоваться следующей проверкой: Код: if (Cardinal(pFile)-Cardinal(pStart))<FileSz then //... Т.е. если адрес байта текущий минус адрес байта начала меньше размера файла, то читать можно, иначе - закрываем файл и выходим. Выводы MMF - крутая и полезная штука, если научиться с ними работать. исходный код моего приложения можно найти во вложении --------- Автор: Владимир Мефисто Special for: Xaker.Name
Ну уж расскажите нам про буферное чтение, и как его применять в данной ситуации, рассмотренной мной в статье.
напрашиваеца вопрос почему до 3х а не до 2х или 4х? :crazy: результаты скорости некоректны, после первого прогона файл полностью кэшируется системой и как намекал некий robt про буфер, то разница будет в пользу буферного чтения а не ММФ
больше чем у кого с чем? ясен фиг что быстрей чем побайтовое чтение, но не быстрей буферного, да и файлы больше двух гиг не осилит
ну держи... для исключения кэширования системой, положи рядом с прогой 2 одинаковых файла приличного размера (1-2гига) с именами test1.dat и test2.dat (для полного задротства можно их дефрагментировать) и после перезагрузки компа запусти также буфер ,в отличии от MMF, можно использовать в многопоточке, типа пока считывается следующая порция файла в это время дрючица текущая в памяти