-
Есть код который разбирает БД=300 мб разбирает долго порядка 30 секунд. Основные тормоза приходятся на функции POS и Copy.
function GetFirstDelim(s:String):Integer; begin Result:=0; Result:=Pos(';',s); if Result<=0 then Result:=Length(s)+1; end;
procedure Separate(var s,s1:String); var p:Integer; begin p:=GetFirstDelim(s); s1:=s; s:=Copy(S1,p+1,Length(S)-p); SetLength(s1,p);
//s1:=Copy(S,1,p-1); //s:=Copy(S,p+1,Length(S)-p); end;
procedure LoadDB(Var List:TList; name:String); var f:TextFile; s,s1:String; FileDesc:PFileDesc; Path:String; SearchRec:TSearchRec; StringList:TStringList; i:Integer; begin FillChar(SearchRec,SizeOf(SearchRec),0);
List.Clear; AssignFile(f,Name); Reset(f); StringList:=TStringList.Create; StringList.LoadFromFile(Name); //while not eof(f) do for i:=0 to StringList.Count-1 do begin //ReadLn(F,s); s:=StringList[i];
Separate(s,s1); Path:=s1; Separate(s,s1);
Separate(s,s1); TryStrToInt(s1,SearchRec.Attr);
Separate(s,s1); TryStrToInt(s1,Integer(SearchRec.FindData.nFileSizeLow));
Separate(s,s1); TryStrToInt(s1,Integer(SearchRec.FindData.nFileSizeHigh));
GetFileDesc(FileDesc, Path, SearchRec); if FileDesc<>nil then List.Add(FileDesc); end; CloseFile(f); end;
-
ну да, можно и бардак считать за базу данных.
-
> Pavia © (29.01.17 00:24)
> Основные тормоза приходятся на функции POS и Copy. >
Переделай на машину состояний, не будет POS, не будет TryStrToInt, не будет кучи лишних действий которые ты совершаешь, причем многократно делая проход по одним и тем же данным, а будет один проход. Обработать 300 мег можно примерно за 1 сек. Выкинь всякие стринглисты, достаточно стрима, лучше TMemoryStream.
-
Не понимаю, чем автомат мне поможет тоже чтение и тоже копирование? Если бы там были числовые значения или любые не текстовые, то прирост был бы. Но тут у меня основную массу составляет текст в его исконной чистой и неизменной форме.
100 мб/с время чтения с диска. Так что за 1 секунду не уложиться. 3 секунды чтение. Латентность кэша составляет 4 такта. Что даёт при чтение по байтно примерно 1٫2 секунды. Пусть запись примерно столько же и того в пределе имеем 5 секунд. Интересно в теории можно ли совместить чтение и разбор данных?
Что касается copy, то она несколько избыточно. Но в целом это не более 2-х раз. Убрал излишнее копирование*. Время разбора сократилось до 12 секунд. POS проходит по базе ровно 1 раз. И на него приходиться 12%.
К TryStrToInt претензий нет она не тормозит.
> Выкинь всякие стринглисты, достаточно стрима, лучше TMemoryStream.
К StringList претензий особо нет кроме того, что он грузит всё в память. Можно выкинуть это тоже несильно ускорит секунды на 3. А вот разбор усложнит.
*)
procedure GetDelimsIndex(const s:string; var indexs:TArrayInteger); var tmp:Integer; i:Integer; begin SetLength(Indexs,BDCountFild); tmp:=0; Indexs[0]:=tmp; for i:=1 to BDCountFild-1 do begin tmp:=PosEx(';',s, tmp+1); if tmp<=0 then tmp:=Length(s)+1; Indexs[i]:=tmp; end; end;
procedure SeparateRow(s:String; indexs:TArrayInteger; var goal:TArrayString); var i:Integer; begin SetLength(Goal, Length(Indexs)-1); for i:=1 to BDCountFild do begin //Goal[i-1]:=Copy(s,Indexs[i-1]+1, Indexs[i]-1-Indexs[i-1]); SetString(Goal[i-1],PChar(@s[Indexs[i-1]+1]), Indexs[i]-1-Indexs[i-1]); // Чуть быстрее чем Copy. end; end;
procedure LoadDB(Var List:TList; name:String); var f:TextFile; s,s1:String; FileDesc:PFileDesc; Path:String; SearchRec:TSearchRec; StringList:TStringList; i:Integer; Row:TArrayString; Indexs:TArrayInteger; begin FillChar(SearchRec,SizeOf(SearchRec),0);
List.Clear; AssignFile(f,Name); Reset(f); StringList:=TStringList.Create; StringList.LoadFromFile(Name); //while not eof(f) do for i:=0 to StringList.Count-1 do begin //ReadLn(F,s); s:=StringList[i];
GetDelimsIndex(s, Indexs); SeparateRow(s, Indexs, Row); path:=Row[0]; TryStrToInt(Row[2],SearchRec.Attr); TryStrToInt(Row[3],Integer(SearchRec.FindData.nFileSizeLow)); TryStrToInt(Row[4],Integer(SearchRec.FindData.nFileSizeHigh));
GetFileDesc(FileDesc, Path, SearchRec); if FileDesc<>nil then List.Add(FileDesc); end; CloseFile(f); end;
Если есть пример подобного то неоткажите покажите свои наработки.
-
Вот для замены Copy. Встроенная Move медленнее чем MoveBytes в 2 раза из-за кейсов. Осталось видимо для совместимости. Можно копировать через FPU. У него выборка быстрее чем у обычных регистров. Хотя видимо от процессоров зависит. Только под себя подпилить надо немного. Она для 64-бит.
procedure MoveBytes(src, dest, count: uint64); asm mov r9, rdi mov r10, rsi
mov rsi, rcx //src mov rdi, rdx //dest mov rcx, r8 //count and r8, 03h //Чистим верхние биты и сохраняем первые 3 бита, чтобы найти остаток shr rcx, 2 //делим на 4, т.к. пишем двойными словами rep movsd mov rcx, r8 //пишем остаток rep movsb
mov rsi, r10 mov rdi, r9 end;
-
Протестировал. Получилось Move 85% от 100% MoveBytes на моем 6950X + ddr4-2666. Но все равно быстрее на 15%. Уже профит.
-
SetLength реже вызывать. Она тормозная. Добавлять блоки заранее. Например так: У меня по крайней мере работает быстрее за счет этого.
procedure TZObject.AddPoint(P: TZPoint); begin //Добавляем память блоками if (FNumPoints mod BlockSize) = 0 then begin SetLength(FPoints, FNumPoints + BlockSize); end; Inc(FNumPoints); FPoints[FNumPoints - 1] := P; end;
-
Если "основные тормоза приходятся на функции POS и Copy", то библиотека QStrings должна решить проблему. В сети она есть.
-
F1 ReadLn
-
Pavia, смотри, я научу тебя плохому! ))
type PMyStrRec = ^TMyStrRec; TMyStrRec = packed record {$IF defined(CPU64BITS)} _Padding: Integer; // Make 16 byte align for payload.. {$ENDIF} codePage: Word; elemSize: Word; refCnt: Integer; length: Integer; // CharPtr : PChar; end;
function Next(var CurPosInBigText:PChar; var tS: TMyStrRec):PString; var c:Char; begin tS.length := 0; tS.CharPtr := CurPosInBigText; c := CurPosInBigText^; while ( c<>#0 ) do begin if ( c = #$D ) then begin CurPosInBigText^ := #0; Inc(CurPosInBigText); if (CurPosInBigText^ = #$A ) then Inc(CurPosInBigText); Break; end; if ( c = ';' ) or ( c = #$A ) then begin CurPosInBigText^ := #0; Inc(CurPosInBigText); Break; end; Inc(tS.length); Inc(CurPosInBigText); c := CurPosInBigText^; end; Result := @tS.CharPtr; end;
function Test:string; const buff = String('c:\mydir\;bla-bla;123;1234;-123456'#$D#$A'd:\mydir\;bla_bla;321;4321;-654321'); var dummy :String; // pMem :PChar; pBuf :PChar; tS :TMyStrRec; // i :Integer; a,b,c :array [1..2] of Integer; Path :array [1..2] of String; // begin // // set abstract string header SetLength(dummy,1); tS := PMyStrRec( PByte(PPointer(@dummy)^) - (SizeOf(tS)-SizeOf(tS.CharPtr) ) )^; //
// // get/set big buffff pMem := AllocMem(Length(buff) + ts.elemSize*2); CopyMemory(pMem, PChar(buff), Length(buff)*SizeOf(Char)); pBuf := pMem; //
// // process for i := 1 to 2 do begin SetString(Path[i], PChar(Next(pBuf,tS)^), tS.length); // 1 Next(pBuf,tS); // 2 TryStrToInt(Next(pBuf,tS)^, a[i]); // 3 TryStrToInt(Next(pBuf,tS)^, b[i]); // 4 TryStrToInt(Next(pBuf,tS)^, c[i]); // 5 end; //
// // result test Result :=Path[1]+Inttostr(a[1])+Inttostr(b[1])+Inttostr(c[1])+ Path[2]+Inttostr(a[2])+Inttostr(b[2])+Inttostr(c[2]);
// FreeMem(pMem); end;
-
> Не понимаю, чем автомат мне поможет тоже чтение и тоже копирование? > > Если бы там были числовые значения или любые не текстовые, > то прирост был бы. Но тут у меня основную массу составляет > текст в его исконной чистой и неизменной форме.
Так текстовые за милую душу обрабатываются конечным автоматом, даже я бы сказал для их обработки это наиболее естественный, хоть и несколько трудоемкий в плане программирования способ. Можно обойтись вообще без копирования.
> 100 мб/с время чтения с диска. Так что за 1 секунду не уложиться. >
Поставь SSD. :) Я про обработку говорил. Уже загруженных данных. Ну прибавь еще на загрузку 3 секунды.
> Латентность кэша составляет 4 такта. Что даёт при чтение > по байтно примерно 1٫2 секунды. Пусть запись примерно столько > же и того в пределе имеем 5 секунд. Интересно в теории можно > ли совместить чтение и разбор данных?
Я тебе предлагаю конечный автомат. В него как раз хоть побайтно можно подавать данные кусками произвольного размера.
> К StringList претензий особо нет кроме того, что он грузит > всё в память. > Можно выкинуть это тоже несильно ускорит секунды на 3. А > вот разбор усложнит.
Не усложнит. Все твои данные могут быть разобраны путем прохода по каждому байту ОДИН раз.
-
> Все твои данные могут быть разобраны путем прохода по каждому байту ОДИН раз.
да там почти так и есть, а вот (2хSetLength + 5xSetString) на каждую строку - это затратно.
-
Как вариант, можно изменить формат хранения данных, что загружать сразу в разобранном виде.
-
11.6 секунд
1 pos 1 move *2 1 pos - второй проход
4*4*300/700=6.8 Плюс чтение 3 секунды=9٫8 И всякая мелочь ещё 2 секунды.
Выкинуть лишние POS который даёт второй проход. Плюс сразу хранить размер строки это избавит ещё от одного pos даст прирост в 2 раза.
> Как вариант, можно изменить формат хранения данных, что > загружать сразу в разобранном виде.
Это отдельный разговор. Если менять, то совсем всё.
-
кстати, задача не сложно распараллеливается.
|