De vorige keer heb ik een aantal Delphi productiviteit tips gegevens. Daarmee kun je een stuk sneller mee programmeren, maar je programma zelf wordt er niet sneller door. Dit keer een aantal snelheids tips over standaard Delphi objecten en componenten.
TStrings
De basisklasse TStrings wordt in Delphi heel veel gebruikt: TMemo, TSqlQuery, etc. Als je hier veel mee werkt, kun je veel snelheidwinst halen door de volgende tips:
Beginupdate/Endupdate
Elke keer als je een Tstrings.String[i] aanpast of toevoegt via TStrings.Add, wordt onder andere een OnChange uitgevoerd. Bijvoorbeeld een TSqlQuery gaat kijken of de SQL een parameter bevat. Dit werkt erg vertragend. Daarom is het beter om het volgende te doen:
Tstrings.BeginUpdate; //lock
try
for i := 0 to 1000 do
TStrings.Add(‘test’);
finally
Tstrings.EndUpdate; //unlock
end
Capacity
Bij veel toevoegingen (zoals boven) is het beter om de capacity gelijk in te stellen op het aantal records, of met stappen te verhogen. Hierdoor wordt niet bij elke “add” de grootte van de interne dynamische array met 1 verhoogd en de oude waarden in de nieuwe array gekopieerd:
Tstrings.Capacity := 1001; //pre allocate space
Text property
TStrings klasse bevat de “Text” property waarbij je de hele inhoud als 1 grote string krijgt. Dit wordt echter bij elke call opnieuw aangemaakt. Dus je kunt beter 1x de string opslaan in een variable en dan bepaalde bewerkingen doen, dan rechtstreeks op via de “text” property te werken:
for i := 0 to Length(TStrings.Text)-1 do //slow!
if TStrings.Text[i] = ‘;’ then //slow!
TStrings.Text[i] := ‘,’; //slow!
Oppassen met name/value pairs.
TStrings heeft handige functies voor “Name=Value” strings. Bijvoorbeeld:
str.Values['Name'] := ‘Value’; //slow
Dit wordt echter intern als een gewone string opgeslagen. Hierdoor wordt elke keer de “IndexOfName” functie gebruikt, die van 0 tot Count-1 alle strings bij langs gaat, en elke string splitst in ‘name’, ‘=’ en ‘value’. Oftewel: TRAAG!
Sorteren
Als je vaak TStrings.IndexOf() functie gebruikt, dan kun je beter TStringlist gebruiken en dan eerst TStringlist.Sort aanroepen. Hierna wordt veel efficiënter gezocht: in plaats van 0 tot Count-1 wordt dan binair gezocht: de waarde op positie 50% wordt vergeleken, als hoger dan op helft van 50% (25%) zoeken, anders op 75% etc. Oftewel steeds op helft. Bij veel strings scheelt dat enorm omdat niet elke string vergeleken wordt, maar meer in blokken.
THashedStringList
Als je nog sneller wilt zoeken, kun je beter een THashedStringList gebruiken. Je moet dan 1x de lijst vullen en daarna sorteren met de “Sort” functie. Dan wordt intern een hash table gemaakt van alle strings (soort database index) zodat het zoeken razendsnel gaat!
TGPIntegerlist
Als je veel integers wilt opslaan, zoeken, bewerken, etc dan is deze klasse goed bruikbaar en snel. Op de volgende site staan meerdere soorten (Int64, floats etc):
http://gp.17slon.com/gp/gplists.htm
TInifile niet gebruiken!
Ik heb zelf ooit de fout gemaakt om TInifile te gebruiken. Dit een wrapper om een aantal Windows API’s voor ini bestanden. Echter, elke keer wordt de hele ini file ingelezen! Dus in de onderstaande code 2x:
TIniFile.WriteString(‘Sectie1′,’Name1′,’Value1′); //slow
TIniFile.WriteString(‘Sectie1′,’Name2′,’Value2′); //slow
Ook als je waardes uitleest wordt elke keer het hele ini bestand ingeladen… Over inefficiente Windows API’s gesproken… Gelukkig heeft Delphi de “TMemInifile”, die 1x het bestand in het geheugen laadt, intern gebruik maakt van THashedStringList, zodat het vele malen sneller werkt.
TFilestream: Oppassen!
Het lijkt zo leuk: files streamen. Maar TFilestream is niet meer dan een soort wrapper om een aantal Windows file API’s heen (readfile, writefile). Het is alleen handig als je bijvoorbeeld het begin van een bestand wilt inlezen, of in blokken. Maar gebruik het NIET op de volgende manier:
fs := TFilestream.Create(‘test.jpg’);
jpg := TJPEGImage.Create;
jpg.LoadFromStream(fs); //slow!
Bij het laden/decomprimeren van de jpg wordt de stream in kleine stukjes uitgelezen. Dit zorgt in het bovenstaande voorbeeld voor dat heel vaak “readfile” aangeroepen wordt. Bij een klant resulteerde dit in 300.000 (!) readfile calls om 4 jpg’s in te laden via het netwerk. Gewoon jpg.LoadFromFile was factor 30x sneller… (of TMemorystream.Copyfrom(TFilestream) gebruiken).
Volgende keer:
Onder andere T(Client)Dataset, TTreeview en TXMLDocument.
Meer Delphi algemeen: setlength, const parameters, resourcestrings, try except/finally, etc.
Feedback:
Reacties? Graag. Vooral over nog meer snelheids tips die hier (nog) niet bij staan!
Opmerking:
De meeste bovenstaande tips heb ik verkregen door af en toe mijn programma te profilen. Mijn ervaring is dat je trage delen juist net vindt waar je het minst verwacht…

