Is const
voor een parameter nuttig of alleen voor nerds en nitpickers?
Moet het zijn: procedure Check(const Text: string);
? Of is procedure Check(Text: string);
hetzelfde?
Misschien vraag je je nooit af hoe de compiler met je code omgaat. En welke effecten dat heeft op je applicatie. Maar als je een beetje weet hoe dit op de achtergrond werkt, is het best slim om het serieus te nemen. Het maakt echt uit voor de prestaties en de grootte van je applicatie.
Met een const parameter kan de compiler de waarde direct aan de variabele koppelen, zonder deze opnieuw te evalueren. Dit wordt constant propagation genoemd. Dit is (meestal) gecombineerd met een techniek genaamd constant folding. Heel eenvoudig gezegd zorgt de compiler ervoor dat variabelen (tijdens het compileren) worden vervangen door constante expressies, zodat ze runtime niet meer geëvalueerd hoeven te worden. Dat betekent dat de compiler je code al kan vereenvoudigen en het aantal runtime evaluaties kan verminderen. En daardoor wordt het gewoon sneller.
Er is één uitzondering en dat zijn parameters die al werken via referentie, zoals het geval is bij objecten. Dat is al geoptimaliseerd. Kleine uitzondering daarop is weer wanneer je met interfaces werkt. Er is nog steeds een klein verschil of je met of zonder const werkt.
Als je dit eenmaal weet, kun je hier ook bewust mee omgaan. En als je dat doet, denk dan niet alleen aan het toevoegen van een const, maar denk (bijvoorbeeld) ook aan code in een lus die variabelen gebruikt. Wat gebeurt er als je de inhoud van een lus omzet naar een procedure of functie met constante parameters? Zoals je je kunt voorstellen, heeft dit positieve effecten op de prestaties. En dat is nog maar een voorbeeld.
Persoonlijk wist ik dat const een optimalisatie van de compiler was, maar niet precies hoe het werkte. De aanleiding om dit nader te bekijken kwam van een ander voorbeeld. Ik kwam dit tegen op het MVP forum. Het is een geweldig voorbeeld dat laat zien dat je je meestal niet bewust bent van het effect op de compiler. Je kunt hier ook je voordeel mee doen omdat het geen ongebruikelijke constructie is.
Het voorbeeld is als volgt:
function GetUserCollection: TUserCollection; begin if FUserCollection = nil then FUserCollection := TUserCollection.Create; Result := FUserCollection; end;
Deze code kan worden verbeterd voor de compiler door de Create naar een andere methode te verplaatsen, zoals dit:
function GetUserCollection: TUserCollection; begin if FUserCollection = nil then InitUserCollection; Result := FUserCollection; end; procedure InitUserCollection; begin FUserCollection := TUserCollection.Create; end;
In de eerste situatie houdt de compiler er rekening mee dat bij elke aanroep van GetUserCollection mogelijk een object moet worden aangemaakt. Hiervoor worden zogenaamde activeringsrecords aangemaakt. Dit is nogal wat overhead, vooral als je zo’n functie in een lus aanroept. In tegenstelling tot de geoptimaliseerde code, waar de compiler alleen rekening hoeft te houden met de aanroep van een procedure.
In het MVP forum werd dit gemeten en elke aanroep in de geoptimaliseerde code werd net zo snel uitgevoerd. De niet-geoptimaliseerde code werd steeds langzamer. Al bij 20 aanroepen duurde het vier keer zo lang om de procedure aan te roepen. De vertraging nam lineair toe en werd significanter met elke extra aanroep.
Dit voorbeeld en de const variabele zijn twee voorbeelden van hoe jouw manier van coderen invloed heeft op wat de compiler wel of niet kan optimaliseren. Met uiteindelijk slechtere of betere prestaties.
Contact