diff --git a/Test/X2UtStringsTest.cfg b/Test/X2UtStringsTest.cfg new file mode 100644 index 0000000..fcec3dd --- /dev/null +++ b/Test/X2UtStringsTest.cfg @@ -0,0 +1,35 @@ +-$A8 +-$B- +-$C+ +-$D+ +-$E- +-$F- +-$G+ +-$H+ +-$I+ +-$J+ +-$K- +-$L+ +-$M- +-$N+ +-$O+ +-$P+ +-$Q- +-$R+ +-$S- +-$T- +-$U- +-$V+ +-$W- +-$X+ +-$YD +-$Z1 +-cg +-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +-H+ +-W+ +-M +-$M16384,1048576 +-K$00400000 +-LE"c:\delphi6\Projects\Bpl" +-LN"c:\delphi6\Projects\Bpl" diff --git a/Test/X2UtStringsTest.dof b/Test/X2UtStringsTest.dof new file mode 100644 index 0000000..e47e955 --- /dev/null +++ b/Test/X2UtStringsTest.dof @@ -0,0 +1,167 @@ +[FileVersion] +Version=6.0 +[Compiler] +A=8 +B=0 +C=1 +D=1 +E=0 +F=0 +G=1 +H=1 +I=1 +J=1 +K=0 +L=1 +M=0 +N=1 +O=1 +P=1 +Q=0 +R=1 +S=0 +T=0 +U=0 +V=1 +W=0 +X=1 +Y=1 +Z=1 +ShowHints=1 +ShowWarnings=1 +UnitAliases=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; +[Linker] +MapFile=0 +OutputObjs=0 +ConsoleApp=1 +DebugInfo=0 +RemoteSymbols=0 +MinStackSize=16384 +MaxStackSize=1048576 +ImageBase=4194304 +ExeDescription= +[Directories] +OutputDir= +UnitOutputDir= +PackageDLLOutputDir= +PackageDCPOutputDir= +SearchPath= +Packages=vcl;rtl;dbrtl;vcldb;vclx;dss;dsnapcrba;dsnapcon;inetdb;webdsnap;websnap;dbxcds;Irc;parsdpk;hotspotter +Conditionals= +DebugSourceDirs= +UsePackages=0 +[Parameters] +RunParams=ip-to-country.csv countries.csv geo.db +HostApplication= +Launcher= +UseLauncher=0 +DebugCWD= +[Version Info] +IncludeVerInfo=1 +AutoIncBuild=0 +MajorVer=1 +MinorVer=0 +Release=0 +Build=0 +Debug=0 +PreRelease=0 +Special=0 +Private=0 +DLL=0 +Locale=1043 +CodePage=1252 +[Version Info Keys] +CompanyName= +FileDescription= +FileVersion=1.0.0.0 +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename= +ProductName= +ProductVersion=1.0.0.0 +Comments= +[Excluded Packages] +c:\delphi6\Bin\dcl31w60.bpl=Delphi 1.0 Compatibility Components +C:\Delphi6\Bin\dcldb60.bpl=Borland Database Components +c:\delphi6\Bin\dclact60.bpl=Borland ActionBar Components +C:\Program Files\madCollection\madBasic\Delphi 6\madHelp_.bpl=madHelp 1.1 · www.madshi.net +c:\delphi6\Bin\applet60.bpl=Borland Control Panel Applet Package +c:\delphi6\Projects\Bpl\X2CompsD6.bpl=X²Software Components - Designtime +C:\Delphi6\Bin\dbx60.bpl=Borland SQL Explorer UI Package +C:\Delphi6\Projects\Bpl\tb2k_d6.bpl=Toolbar2000 Components (Jordan Russell) +c:\delphi6\Projects\Bpl\tb2kdsgn_d6.bpl=Toolbar2000 Design Package (Jordan Russell) +c:\delphi6\Projects\Bpl\VirtualTreesD6D.bpl=Virtual Treeview +c:\delphi6\Bin\DCLNMF60.bpl=NetMasters Fastnet Tools +c:\delphi6\Bin\dclado60.bpl=Borland ADO DB Components +c:\delphi6\Bin\dclclxdb60.bpl=Borland CLX Database Components +C:\Delphi6\Bin\dclclxstd60.bpl=Borland CLX Standard Components +c:\delphi6\Bin\dclie60.bpl=Internet Explorer Components +C:\Delphi6\Projects\Bpl\aSQLitepkg.bpl=Aducom Software -- SQLite RunTime Components +c:\delphi6\Projects\Bpl\asqlite.bpl=Aducom Software -- SQLite Design Time Components +c:\delphi6\Projects\Bpl\PCtrlExd6.bpl=PageControlEx +c:\delphi6\Projects\Bpl\JvCoreD6D.bpl=JVCL Core Components +c:\delphi6\Projects\Bpl\JvSystemD6D.bpl=JVCL System Components +c:\delphi6\Projects\Bpl\JvStdCtrlsD6D.bpl=JVCL Standard Controls +c:\delphi6\Projects\Bpl\JvCtrlsD6D.bpl=JVCL Visual Controls +c:\delphi6\Projects\Bpl\JvCmpD6D.bpl=JVCL Non-Visual Components +c:\delphi6\Projects\Bpl\JvCustomD6D.bpl=JVCL Custom Controls +c:\delphi6\Projects\Bpl\JvDlgsD6D.bpl=JVCL Dialog Components +c:\delphi6\Projects\Bpl\JvCryptD6D.bpl=JVCL Encryption and Compression Components +c:\delphi6\Projects\Bpl\JvMMD6D.bpl=JVCL Multimedia and Image Components +c:\delphi6\Projects\Bpl\JvNetD6D.bpl=JVCL Network Components +c:\delphi6\Projects\Bpl\JvAppFrmD6D.bpl=JVCL Application and Form Components +c:\delphi6\Projects\Bpl\JvDBD6D.bpl=JVCL Database Components +c:\delphi6\Projects\Bpl\JvBDED6D.bpl=JVCL BDE Components +c:\delphi6\Projects\Bpl\JvInterpreterD6D.bpl=JVCL Interpreter Components +c:\delphi6\Projects\Bpl\JvBandsD6D.bpl=JVCL Band Objects +c:\delphi6\Projects\Bpl\JvPluginD6D.bpl=JVCL Plugin Components +c:\delphi6\Projects\Bpl\JvJansD6D.bpl=JVCL Jans Components +c:\delphi6\Projects\Bpl\JvGlobusD6D.bpl=JVCL Globus Components +c:\delphi6\Projects\Bpl\JvPrintPreviewD6D.bpl=JVCL Print Preview Components +c:\delphi6\Projects\Bpl\JvPageCompsD6D.bpl=JVCL Page Style Components +c:\delphi6\Projects\Bpl\JvValidatorsD6D.bpl=JVCL Validators and Error Provider Components +c:\delphi6\Projects\Bpl\JvUIBD6D.bpl=JVCL Unified Interbase Components +c:\delphi6\Projects\Bpl\JvWizardD6D.bpl=JVCL Wizard Design Time Package +c:\delphi6\Projects\Bpl\JvTimeFrameworkD6D.bpl=JVCL Time Framework +c:\delphi6\Projects\Bpl\JvHMID6D.bpl=JVCL HMI Controls design time unit +c:\delphi6\Projects\Bpl\JvManagedThreadsD6D.bpl=JVCL Managed Threads +c:\delphi6\Projects\Bpl\JvXPCtrlsD6D.bpl=JVCL XP Controls +c:\delphi6\Projects\Bpl\JvDockingD6D.bpl=JVCL Docking Components +c:\delphi6\Projects\Bpl\JvDotNetCtrlsD6D.bpl=JVCL DotNet Controls +c:\delphi6\Projects\Bpl\dclIndyCore60.bpl=Indy 10 Core Design Time +c:\delphi6\Projects\Bpl\dclIndyProtocols60.bpl=Indy 10 Protocols Design Time +c:\delphi6\Projects\Bpl\SysILS.bpl=System ImageList +c:\delphi6\Projects\Bpl\DragDropD6.bpl=Drag and Drop Component Suite +C:\Projects\Components\DevExpress\OrgChart Suite\Lib\dcldxOrgCD6.bpl=ExpressOrgChart by Developer Express Inc. +C:\Projects\Components\DevExpress\OrgChart Suite\Lib\dcldxDBOrD6.bpl=ExpressDBOrgChart by Developer Express Inc. +c:\delphi6\Projects\Bpl\BalloonD6.bpl=Balloon 2.0 +c:\delphi6\Projects\Bpl\DIPasDocD6.bpl=DiPasDoc - Designtime +C:\Delphi6\Projects\Bpl\DIContainers_D6.bpl=The Delphi Inspiration -- DIContainers +c:\delphi6\Bin\dclshlctrls60.bpl=Shell Control Property and Component Editors +c:\delphi6\Bin\dclsmp60.bpl=Borland Sample Components +c:\delphi6\Bin\dclbde60.bpl=Borland BDE DB Components +c:\delphi6\Bin\dclcds60.bpl=Borland Base Cached ClientDataset Component +C:\Delphi6\Bin\dclmid60.bpl=Borland MyBase DataAccess Components +c:\delphi6\Bin\dclbdecds60.bpl=Borland Local BDE ClientDataset Components +c:\delphi6\Bin\dclib60.bpl=InterBase Data Access Components +c:\delphi6\Bin\DBWEBXPRT.BPL=Borland Web Wizard Package +c:\delphi6\Bin\dcloffice2k60.bpl=Microsoft Office 2000 Sample Automation Server Wrapper Components +c:\delphi6\Bin\dcltee60.bpl=TeeChart Components +c:\delphi6\Bin\dcltqr60.bpl=TeeChart for QuickReport Components +c:\delphi6\Bin\dclnet60.bpl=Borland Internet Components +c:\delphi6\Bin\dclite60.bpl=Borland Integrated Translation Environment +c:\delphi6\Bin\dcldbx60.bpl=Borland dbExpress Components +c:\delphi6\Bin\dclsoap60.bpl=Borland SOAP Components +c:\delphi6\Bin\dclocx60.bpl=Borland Sample Imported ActiveX Controls +c:\delphi6\Bin\dcldbxcds60.bpl=Borland Local DBX ClientDataset Components +C:\Program Files\madCollection\madRemote\Delphi 6\madRemote_.bpl=madRemote 1.1b · www.madshi.net +C:\Program Files\madCollection\madKernel\Delphi 6\madKernel_.bpl=madKernel 1.3 · www.madshi.net +C:\Program Files\madCollection\madCodeHook\Delphi 6\madCodeHook_.bpl=madCodeHook 2.1b · www.madshi.net +C:\Program Files\madCollection\madSecurity\Delphi 6\madSecurity_.bpl=madSecurity 1.1n · www.madshi.net +C:\Program Files\madCollection\madShell\Delphi 6\madShell_.bpl=madShell 1.3k · www.madshi.net +C:\WINDOWS\System32\ibevnt60.bpl=Borland Interbase Event Alerter Component +c:\delphi6\Projects\Bpl\PsychoTidyD6.bpl=PsychoTidy IDE Expert +[HistoryLists\hlUnitAliases] +Count=1 +Item0=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; diff --git a/Test/X2UtStringsTest.dpr b/Test/X2UtStringsTest.dpr new file mode 100644 index 0000000..fcb6886 --- /dev/null +++ b/Test/X2UtStringsTest.dpr @@ -0,0 +1,144 @@ +program X2UtStringsTest; + +{$APPTYPE CONSOLE} + +uses + SysUtils, + Windows, + + FastStrings, + + X2UtStrings; + +var + GFreq: Int64; + GStart: Int64; + +procedure TimeStart(); +begin + QueryPerformanceFrequency(GFreq); + QueryPerformanceCounter(GStart); +end; + +procedure TimeEnd(); +var + iEnd: Int64; + +begin + QueryPerformanceCounter(iEnd); + WriteLn(Format('%.6f seconds', [(iEnd - GStart) / GFreq])); +end; + + +procedure OldSplit(const ASource, ADelimiter: String; out ADest: TSplitArray); +var + iCount: Integer; + iPos: Integer; + iLength: Integer; + sTemp: String; + +begin + sTemp := ASource; + iCount := 0; + iLength := Length(ADelimiter) - 1; + + repeat + iPos := Pos(ADelimiter, sTemp); + + if iPos = 0 then + break + else begin + Inc(iCount); + SetLength(ADest, iCount); + ADest[iCount - 1] := Copy(sTemp, 1, iPos - 1); + Delete(sTemp, 1, iPos + iLength); + end; + until False; + + if Length(sTemp) > 0 then begin + Inc(iCount); + SetLength(ADest, iCount); + ADest[iCount - 1] := sTemp; + end; +end; + +procedure OldFastStringsSplit(const ASource, ADelimiter: String; out ADest: TSplitArray); +const + BufferSize = 50; + +var + iCount: Integer; + iSize: Integer; + iPos: Integer; + iDelimLength: Integer; + iLength: Integer; + iLastPos: Integer; + +begin + iCount := 0; + iDelimLength := Length(ADelimiter); + iLength := Length(ASource); + iPos := 1; + iLastPos := 1; + iSize := BufferSize; + SetLength(ADest, iSize); + + repeat + iPos := FastPos(ASource, ADelimiter, iLength, iDelimLength, iPos); + + if iPos = 0 then + break + else begin + ADest[iCount] := Copy(ASource, iLastPos, iPos - iLastPos); + Inc(iPos, iDelimLength); + iLastPos := iPos; + + Inc(iCount); + if iCount >= iSize then begin + Inc(iSize, BufferSize); + SetLength(ADest, iSize); + end; + end; + until False; + + + if iLastPos <= iLength then begin + ADest[iCount] := Copy(ASource, iLastPos, iLength - iLastPos + 1); + Inc(iCount); + end; + + if iSize <> iCount then + SetLength(ADest, iCount); +end; + + +var + sTest: String; + iCount: Integer; + aSplit: TSplitArray; + +begin + sTest := 'this|isateststring||'; + for iCount := 0 to 7 do + sTest := sTest + sTest; + + TimeStart(); + Write('10.000 iterations of OldSplit: '); + for iCount := 0 to 9999 do + OldSplit(sTest, '|', aSplit); + TimeEnd(); + + TimeStart(); + Write('10.000 iterations of OldFastStringsSplit: '); + for iCount := 0 to 9999 do + OldFastStringsSplit(sTest, '|', aSplit); + TimeEnd(); + + TimeStart(); + Write('10.000 iterations of Split: '); + for iCount := 0 to 9999 do + Split(sTest, '||', aSplit); + TimeEnd(); + + ReadLn; +end. diff --git a/X2UtStrings.pas b/X2UtStrings.pas index ec9a962..ec4266d 100644 --- a/X2UtStrings.pas +++ b/X2UtStrings.pas @@ -8,6 +8,9 @@ unit X2UtStrings; interface +type + TSplitArray = array of String; + //:$ Formats the specified size //:: If KeepBytes is true, the size will be formatted for decimal separators //:: and 'bytes' will be appended. If KeepBytes is false the best suitable @@ -30,6 +33,9 @@ interface //:$ Compares AMatch against AAgainst using AAgainst's length. function SameTextS(const AMatch, AAgainst: String): Boolean; + //:$ Splits a string on the specified delimiter + procedure Split(const ASource, ADelimiter: String; out ADest: TSplitArray); + implementation uses SysUtils; @@ -101,5 +107,127 @@ begin Result := SameTextL(AMatch, AAgainst, Length(AAgainst)); end; + +procedure Split; + // StrPos is slow. Sloooooow slow. This function may not be advanced or + // the fastest one around, but it sure kicks StrPos' ass. + // 11.5 vs 1.7 seconds on a 2.4 Ghz for 10.000 iterations, baby! + function StrPosEx(const ASource, ASearch: PChar): PChar; + var + pPos: PChar; + pSub: PChar; + + begin + Result := nil; + + // Search for the first character + pPos := ASource; + + while pPos^ <> #0 do + begin + if pPos^ = ASearch^ then + begin + // Found the first character, match the rest + pSub := ASearch; + Result := pPos; + Inc(pSub); + Inc(pPos); + + + while pSub^ <> #0 do + begin + if pPos^ <> pSub^ then + begin + // No match, resume as normal + Result := nil; + break; + end; + + Inc(pSub); + Inc(pPos); + end; + + // If still assigned, all characters matched + if Assigned(Result) then + exit; + end else + Inc(pPos); + end; + end; + +const + GrowStart = 32; + GrowMax = 256; + +var + iCapacity: Integer; + iCount: Integer; + iDelimLen: Integer; + iLength: Integer; + iPos: Integer; + iSize: Integer; + pDelimiter: PChar; + pLast: PChar; + pPos: PChar; + +begin + // Reserve some space + iCapacity := GrowStart; + iCount := 0; + SetLength(ADest, iCapacity); + + iDelimLen := Length(ADelimiter); + iLength := Length(ASource); + iPos := -1; + pDelimiter := PChar(ADelimiter); + pPos := PChar(ASource); + + repeat + // Find delimiter + pLast := pPos; + pPos := StrPosEx(pPos, pDelimiter); + + if pPos <> nil then + begin + // Make space + Inc(iCount); + if iCount > iCapacity then + begin + if iCapacity < GrowMax then + Inc(iCapacity, iCapacity) + else + Inc(iCapacity, GrowMax); + + SetLength(ADest, iCapacity); + end; + + // Copy substring + iSize := Integer(pPos) - Integer(pLast); + SetString(ADest[iCount - 1], pLast, iSize); + + // Move pointer + Inc(pPos, iDelimLen); + Inc(iPos, iSize + iDelimLen); + end else + begin + if iPos < iLength then + begin + // Copy what's left + Inc(iCount); + if iCount > iCapacity then + SetLength(ADest, iCount); + + ADest[iCount - 1] := pLast; + end; + + if iCount <> iCapacity then + // Shrink array + SetLength(ADest, iCount); + + break; + end; + until False; +end; + end.