Is const
for a parameter useful or only for nerds and nitpickers?
Should it be: procedure Check(const Text: string);
? Or is procedure Check(Text: string);
basically the same?
Maybe you never ask yourself how the compiler handles your code. And what effects that has on your application. But if you know a bit about how this works in the background, it’s pretty smart to take it seriously. It really matters for the performance and size of your application.
A constant parameter allows the compiler to associate the value directly with the variable, without having to re-evaluate it. This is called constant propagation. This is (usually) combined with a technique called constant folding. In very simply terms, the compiler ensures that variables are replaced (during compilation) by constant expressions, so they no longer need to be evaluated runtime. That means the compiler can already simplify your code and reduce the amount of runtime evaluations. And as a result, it simply becomes faster.
There is one exception and that is parameters that already work by reference, as the case for objects. That is already optimised. Small exception to that again is when you work with interfaces. There is still a small difference if you work with or without const.
Once you know this, you can also act intentionally with this. And if you do, don’t just think about adding a const, but also think (for example) about code in a loop that uses variables. What happens if you convert the contents of a loop to a procedure or function with constant parameters? As you can imagine, this has positive effects on performance. And that is just an example.
Personally, I knew that const was a compiler optimization, but not exactly how it worked. The trigger to take a closer look at this came from another example. I came across this on the MVP forum. It is a great example which shows that you are usually unaware of the effect on the compiler. You can also take advantage of this as it is not an unusual construction.
The example is as follows:
function GetUserCollection: TUserCollection; begin if FUserCollection = nil then FUserCollection := TUserCollection.Create; Result := FUserCollection; end;
This code can be improved for the compiler by moving the Create to another method, like this:
function GetUserCollection: TUserCollection; begin if FUserCollection = nil then InitUserCollection; Result := FUserCollection; end; procedure InitUserCollection; begin FUserCollection := TUserCollection.Create; end;
In the first situation, the compiler takes into account that, in every call to GetUserCollection, it is possible that an object needs to be created. So-called activation records are prepared for this purpose. This is quite some overhead, especially if you call such a function in a loop. Unlike the optimised code, where the compiler only has to take into account the call to a procedure.
In the MVP forum, this was measured and every call in the optimised code was executed just as fast. The non-optimised code, got slower and slower. Already at 20 calls, it took four times as long to call the procedure. The delay increased linearly, becoming more significant with each additional call.
This example and the const variable are two examples of how your way of coding affects what the compiler can or cannot optimize. With worse or better performance in the end.