All hail the “const” parameters!

Previous: Introduction

How does it manifests itself in Profiling?

A missing “const” can manifests itself in different ways during profiling, for String, Interface and dynamic array types, you’ll usually see it via the relevant xxxAddRef or xxxClr functions (the obvious case) and via time spent in begin/end (the less obvious case). Depending on how many functions calls with the reference counting are involved in your inner loop, none of the above may come ahead, even if calls and reference counting are an issue: the time spent will be spread over the above functions and your various begin/end sections.
For record parameters, the lack of “const” could materialize itself in “Move“, or other data copying costs (large records will be moved, smaller ones can be copied via ad-hoc in-place code by the compiler).

In multi-threading, things can be a little less obvious, as rather than the reference counting and exception frame, it could be the bus locks hitting you, or sometimes worse, inter-CPU traffic to pass around the values of the reference counter.

Multi-threading’s worst case would be referring to the same string field from multiple threads, and passing that string around in functions without “const” parameters, thus repeatedly hitting the reference counter of that string from multiple threads at the same time, resulting in extra inter-CPUs traffic to share the value of that reference counter.

In such cases, “const” can help, but it will often not be enough, you’ll also have to look for functions/methods that return strings with an unnecessary reference counting, like when getting your strings from a TStringList.

Good Practice

Usually I tend to “const” just about every parameter that isn’t “var” or an object, this isn’t just  to pre-emptively alleviate possible performance issues, but also to gain something valuable when debugging and writing a function’s body: untouched input parameters, wherever you are in the function’s body. This means not just in the function’s body, but also when in the functions called by the function.

And that’s the key point to remember about “const” parameter: it’s here to state that a parameter will be left unchanged inside the body, something which allows the compiler to optimize it’s output, and the developer to optimize his thought process too. Qualifying all your parameters as either “const” or “var” makes your intentions obvious.

So IME, it’s not worth it to hoard your keystrokes, best spend them on “const” and explicit local variables when you really need to alter those input parameters.
Whoever has to maintain your code will be thankful.

Object parameters

There is one case I’ve not mentioned in all the above, the case of object parameters (and class references). It’s a special case because when you pass an object as “const“, you’re not making any promises of not keeping the object constant, but just keeping the underlying variable (pointer) constant. This is unlike other languages. There is also a risk of confusion when your code will be read by developers not really familiar with the underlying pointer-based mechanics of objects (and they are more common than you may think, even, and maybe more so in highly OO languages).

So for objects, unless you want to spend a lot of time educating, my rule of thumb is to avoid “const” or “var” for object parameters.

Beyond “const” parameters

Const’ing your parameters will eliminate some defensive copying, implicit try…finally, and reference counting overhead, but it leaves open another field of similar inefficiencies: that of return values.

Alas there is no “const Result” syntax, which would allow to state that your result can’t be altered in any way (a sort of strict immutability if you will). Thus whenever you have a function returning a String f.i., you’ll end up triggering the implicit reference counting and exception machinery if you’re not careful (like in this case)…

There are strategies to tackle this issue, but none of them are as simple as adding a “const“, and most of them come with sacrifices… so, let them be food for other articles.

15 thoughts on “All hail the “const” parameters!

  1. You say that even if you want to modify the string, you should still use const. Does that means that

    function DoSomething(AString:String);
    // modify AString…

    is slower than

    function DoSomething(const AString:String);
    LString: String;
    LString := AString;
    // modify LString…

  2. @Rob: in your second listing, the implicit try/finally will be on your local variable in stead of the parameter.
    So there is no netto difference between the two listings.


  3. Rob: I was wondering the samething. I cabn’t test it at the moment, but I think that bit was a mistake in the anakysis. The rest of the article is quite good, thoug. 🙂

  4. @Rob McDonell
    In practice it’ll depend on how you modify the string, and how many times you’ll access the original string during or after its modification.
    Thing is, the copy on write mechanism ensures that in the worst case the const version will only be marginally slower, while best case can be faster.
    If your function is a very high frequency critical one, won’t evolve much, and you’ve already optimized all that can be, then you could worry about trying to test if removing the ‘const’ brings a plus, though usually there may be better strategies (usually revolving around how and why you modify the passed string).

    In all other cases, my advice would be to stick to the safety of ‘const’. There are benefits in terms of debugging, resilience of performance to implementation changes, and maintainability, as it’s simpler to check that all strings are passed as ‘const’ in a library rather than check case by case if it’s worth it not using ‘const’ in some cases 😉

  5. I so fully agree that I wish the default for passing parameters had been const instead of passing them by value.
    Then if you’d really need to modify the parameter inside the function you could opt out and specifically choose by ref or by value…

  6. const string parameters is always a good idea.

    But perhaps it worth saying a word about integer and floating-point parameters: there is no difference in code generation for integer and floating-point values. There is only a difference in usage inside the function/procedure/method: if defined as const, you can’t assign another value to them.

  7. I have been “const”-ing for about 10 years. Now I have a good “Look – I told you” article to refer to. Thank you for your observations.

  8. Unfortunately if you have the “{$STRINGCHECKS ON}” (what is the default value) in Delphi 2009/2010. The “const” becomes irrelevant, because the compiler will always emit the implicit try/finally and the code that manages the RefCount. That’s why I disable this stupid compiler option in all of my projects. StringChecks are for not correctly migrated C++Builder projects. Delphi projects experience a bad hit in string-performance.

  9. @Andreas Hausladen

    That is so good to know, I am just learlning the Delphi-Unicode world. Not sure about the timeframe when I really need to make the jump, but I’ve been looking it past few days, and porting some old code for D2009+ and I surely did not know about that option. Is there option in projeect level, or do I need to use .Inc file for that?

  10. Another case when “CONST” makes a big difference : open arrays.

    If you call :
    function DoSmth( aArray : array of Integer );
    DoSmth(myArray) will actually make a *complete copy of myArray’s content on the stack*.

    On the other hand :
    function DoSmth( const aArray : array of Integer );
    DoSmth(myArray) will only take a pointer to myArray.

    And obviously, what is mentioned in this post also apply to “var” arguments – together with the advantages and dangers of having parameters passed as reference.

Comments are closed.