Optimizing ARC the hard way

One of the potentially unnecessary ARC triggers is passing a reference counted entity as a parameter. Depending on the parameter's declaration, the compiler might insert transient reference counting code in the prologue and epilogue of the called procedure (function, method).

Parameters passed with the default (value) semantics will trigger the reference counting mechanism. On the other hand, parameters passed as references - declared as var, const, [ref], [weak] or [unsafe] - will not trigger the reference counting mechanism while entering and exiting the procedure.

There is nothing new here. Delphi COW copy-on-write strings are reference counted, and passing strings as const parameters is a commonly used optimization pattern. The same principle applies not only to strings, but also to all other reference counted entities, like dynamic arrays, variants, interface references, anonymous method references, and additionally object references on Delphi ARC compilers.

Why the hard way?

There is nothing inherently hard about adding const before a parameter.

Well, the real problem is not in the process of marking parameters as const, but in the implications of such a modification. Changing the signatures of any non-private procedure (function, method) is an API-breaking change. If you don't have to think about backward compatibility or maintaining a stable interface for interacting with other applications or code, then const parameter optimization is trivial. Something that could be done by a ten-year-old.

But if you do care about backward compatibility or keeping a stable interface - if breaking the API layer is a concern - then you can expect nothing short of blood, sweat and tears.

Let's see...

Code using strings is usually already optimized. This optimization pattern has been used in Delphi for so long, that even if you stumble upon code that is not optimized, it will probably not be worth while changing it - or you would be able to do it without breaking a sweat.

Interfaces are a slightly newer thingy. Even though they have been introduced way back in Delphi 4, they are not as widespread, and there is a good chance that code passing interfaces is not as optimized as code passing strings. Also, there is a good chance that such code is not particularly worth optimizing. Occasional calls to such methods can hardly represent bottlenecks. And if you find yourself having to optimize such calls, where the change is really a breaking change, chances are that there is not much of such code lying around and that breaking change, no matter how painful, will be more or less localized and tolerable.

But, when was the last time you passed an object reference as const parameter? And an even better question - when was the last time you saw Delphi frameworks passing object references as const?

Ok, if you have been using ARC compilers or you have been looking at some newly-written code, then marking objects as const is most likely old news to you.

But if you have been solely living in Windows land, using the classic Delphi compiler, then your answer would be radically different.

And this is where the real screaming begins - with no end in sight.

Just like you didn't mark object parameters as const in your code, developers writing the Delphi frameworks' code didn't do that either. Why should they? Before the ARC compiler there was really no difference between passing that reference as a value or as a constant, and nobody writes code that has no purpose whatsoever.

Just take a look at the TComponent code and everything built upon it. Almost everything - except the most recent code here and there - uses the value semantic for passing object parameters.

All notification events pass at least one object instance, Sender, and they pass it as a value parameter. All methods used for the TComponent free notification system pass objects as value parameters, too.

And it is not like those methods are called once in a while. They are extensively used everywhere, from form serialization to timer notifications, tracking mouse and key events, gestures, just to name a few places. You don't need to measure the performance impact to know it is there.

It is not just about your code, which you may change or not as you please. It is about the very core foundations every Delphi codebase is built upon. And that foundation is anything but ARC-friendly. That would not be a problem if ARC and non-ARC compilers would have completely separated frameworks. But they don't. The promise of single cross-platform code across all compilers turns into a curse one more time.

Optimizing those frameworks for ARC would imply a serious API-breaking change in almost every single method across shared core classes. All code would be affected, ARC or non-ARC. All third party code would have to be changed. All customer code, too.

On one hand, if we could forget about maintaining backward compatibility, it would be a rather trivial one-time change to make.

On the other hand, for anyone that must maintain code compatibility.... blood, sweat and tears would probably look like an understatement.

A perfect "Damned if you do, damned if you don't" scenario.


Popular posts from this blog

Coming in Delphi 12: Disabled Floating-Point Exceptions

Assigning result to a function from asynchronous code

Beware of loops and tasks