All hail the “const” parameters!

Passing parameters as “const” is a classic Delphi optimization trick, but the mechanisms behind that “trick” go beyond cargo-cult recipes, and may actually stumble into the “good practice” territory.

Why does it work?

The most well known case is “const String“, for which the compiler than takes advantage of the “const” to pass the String reference directly (as a pointer)… and without increasing the reference counter.

To illustrate what the reference counting implies, here are two screen-shots from the CPU disassembly view, taken in Delphi 6, but the recent compilers up to Delphi 2010 at least behave in a similar fashion (just with the Unicode version of the functions).
I’ve made a pseudo-function, with only one String parameter that calls a single function (here Length(), which isn’t inlined in Delphi 6, and corresponds to LStrLen), guess which CPU disassembly corresponds to “Test(const a : String)” and which to “Test(a : String)

What you’re seeing on the left is an implicit try…finally construct which is used to protect the reference counting (LStrAddRef/LStrClr) on the implicit local variable used to store the String parameter. If you have such calls nested a few levels deep (not uncommon in object-oriented code), you could accumulate quite some overhead.
Also not see here but hidden within the AddRef and Clr calls are bus locks, which may hit you disproportionately in multi-threading scenarios.

And what if you’re modifying the passed String? Can you forego the “const“? Well no, the extra overhead is still there, and using a “const” still beneficial.

String isn’t the only type affected, there are similar gains for all reference counted types (interfaces, dynamic arrays, records holding reference-counted types). And when passing a record as “const“, there is an additional gain in the lack of defensive copying of the record (the larger the record, the greater the gain).

For ordinal and numeric types, there is no compiler optimization (yet), but it can be good practice to mark them as “const“, not in the optimistic hope that someday the compiler will optimize it, but to ease up debugging and code writing. Copies of those parameters (when you want to modify them in the function body) are typically handled adequately by the compiler (and often enough comes for free), so don’t let performance considerations hold you.

There is one case in which using “const” could have adverse effects, but it involves global variables which you would modify or release during the call… so you don’t really need to know more about it, do you? 😉

Next: Spotting the issues, Good practices

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);
    begin
    // modify AString…

    is slower than

    function DoSomething(const AString:String);
    var
    LString: String;
    begin
    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.

    –jeroen

  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.