Главная » Delphi » Произвольный доступ к большим массивам данных

0

Если мы будем получать данные с СОМ-порта с экстремальными значениями выставленного нами самими скоростного порога — 32 Кбайта в секунду, то объем данных достигнет величины в несколько десятков мегабайт уже минут за двадцать. А что делать, если нужно что-то отслеживать сутками? Даже

GPS-иавигатор, который мы изучали в главе 20, при своих никчемных 4800 бод выдает каждую секунду примерно 500 байт информации, которую приходится принимать, а уж потом в ней разбираться, т. е. за сутки он "накидает" нам примерно 40 Мбайт. Такие массивы в памяти — предел для нормальной работы среднего компьютера под управлением Windows, который параллельно еще и выполняет другие задачи, и увеличение объема ОЗУ тут проблемы, естественно, принципиально не решит. Поэтому периодически надо данные сбрасывать на диск, не полагаясь на Windows. Как это лучше сделать?

Для измерения времени работы программы добавим к нашему пробному приложению модуль DateUtils. Модельная задача будет такая: накапливать в памяти данные, добавляя по одному байту, а при превышении некоего порога сбрасывать их на диск. Отметим, что реальные задачи сложнее — сбрасывать обычно надо не весь поток, по ходу дела нужно получать доступ к файлу, например, из другого "треда" и т. п. Мы же сейчас хотим сравнить механизмы накопления в "голом" виде. Сначала попробуем то, что нам предлагает механизм TSrteam.

Дополним список переменных следующим:

Stream,FStream:TStream; t.t, told:TDateTime;

В процедуре инициализации запишем:

procedure TForml.FormCreate(Sender: TObject); begin

Stream:=TMemoryStream.Create;

FStream:=TFileStream.Create(‘temp.tmp’,fmCreate); I перезаписываем I

FStream.Free; (начнем потом с нуляI

end;

По нажатию кнопки будем делать следующее:

procedure TForml.ButtonlClick(Sender: TObject);

var st:string;

begin

St ream. Posi t ion: =0 ; Memol.Lines.Clear; i.: =0 ;

told:=Time;

while i<209715200 do (до 200 Мбайт данных} begin

Stream.Write(byte(i),1);

if Stream.Size>5*1048576 /если превысило 5 Мбайт}

then

begin

FStream:=TFileStream.Create

(‘temp.tmp’,fmOpenReadWrite or fmShareDenyNone); FStream. Position:=FStream.Size; Stream.Position:=0; {с нуля}

FStream.CopyFromtStream,Stream.Size); (копируем в файл) Stream.Free; {опять с нуля} FStream.Free; {опять с нуля} Stream:=TMemoryStream.Create; end; i:=i+l;

Application.ProcessMessages; end;

FStream:=TFileStream.Create

(‘temp.tmp’,fmOpenReadWrite or fmShareDenyNone); FStream.Position:=FStream.Size; Stream.Position:»=0; {с нуля) FStream.CopyFrom(Stream, Stream.Size);

{копируем в файл концовку массива} Stream.Free; {все удаляем) FStream.Free; (все умаляем} tt:=Time; {измеряем время) st:=IntToStr(SecondsBetween(tt,told)); st:=IntToStr(i)+’ ‘+st+’ s'; Memol.Lines.Add(st); end;

В соответствии с этим кодом программа будет в непрерывном цикле накапливать значения байтов от 0 до 255 сначала в массиве stream в памяти и при превышении порога в 5 Мбайт сбрасывать его на диск через файловый поток FStream в файл temp.tmp. Работа закончится, когда общий объем файла составит 200 Мбайт. Порог массива в памяти в 5 Мбайт я выбрал вынужденно — при попытке сделать больше программа исправно выдавала "Out of memory". С чем это связано и почему именно 5 Мбайт— я выяснить не смог, возможно, это просто особенности функционирования класса TStream. Отметим данный факт в крупный минус методу и запустим программу. Вот что выводится в Memo по окончании процедуры:

209715200 846 s

То есть программа работала более 14 минут (разумеется, на другом компьютере и в других условиях результаты будут иными). Можно попробовать ускорить процесс, если не уничтожать и не создавать заново файловый поток каждый раз, НО тогда вне зависимости ОТ ВСЯКИХ fmShareDenyNone просматривать его по ходу дела не получается. Запишем это также в минус.

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

type

bytep="byte; var

Forml: TForml;

pp:pointer; (нетипизированный указатель) pint:integer; (указатель-число) i,j,x:integer; (счетчики) xb:byte;

pb:bytep; (указатель на байт) ff:file;

tt,told:TDateTime;

Процедура инициализации:

procedure TForml.FormCreate(Sender: TObject); begin

getmem(pp,5*1048576); (резервируем 5 Мбайт)

pint:=longint(pp);

assignfile(ff,’temp.tmp’);

rewrite (ff, 1); (переписываем с нуля)

x:=5*1048576; (это будет объем записи)

end;

Собственно тестовая процедура:

prooedure TForml.ButtonlClick(Sender: TObject);

var st:string;

begin

Memol.Lines.Clear; i:=0;j:=0; told:=Time;

while i<209715200 do (до 200 Мбайт данных) begin

if i>=(j+1)*5*1048576 then (если равно 5 Мбайт)

begin

j:=j+l;

reset (ff,l); (открываем заново) Seek(ff, FileSize(ff)); (позицию – в конец файла) blockwrite(ff,ppA,x) ; closefile(ff) ; end;

xb:=byte(i) ;

pb:-bytep(pint+i-(j*5*1048576)); (после каждых 5 Мбайт с нуля) pb~:=xb; i:=i-t-l;

Application.ProcessMessages; end;

reset(f?,1); Seek(ff,FileSize(f f)); blockwrite(ff,pp",x); close?ile(ft") ; tt:=Time; (измеряем время) st:=IntToStr(SecondsBetween(tt,told)) ; st:=IntToStr(i)+’ ‘+st+* s'; Memol.Lines.Add(s t) ; end;

He забудем также освободить зарезервированную память в процедуре onDestroy. Результат будет несколько ошеломляющим:

209715200 237 s

То есть скорость работы возросла почти в четыре раза! Если вы поэкспериментируете с этим алгоритмом, то выясните, что скорость практически не зависит от того, открываем ли мы файл каждый раз заново или нет. Это при том, что у нас не возникает никаких проблем с размером резервируемой области. В пределах объема массива в памяти от 1 до 10 Мбайт скорость работы не меняется. Зато в обоих случаях скорость (в Windows 98) падала, когда программу сворачивали в панель задач. Из всего изложенного можно сделать вывод, что процедуры на основе самостоятельно организованных массивов с записью непосредственно в память предпочтительнее "официального" механизма — возможно, что и потоковый механизм можно "вылизать" так, чтобы он работал, "как часы", но зачем?

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии