Directions for ARC Memory Management in Delphi
posted by marcocantu
With the coming 10.3 release, RAD Studio R&D and PM teams are making Delphi's new directions in memory management official.
A few years back, when Embarcadero started building new Delphi compilers for platforms other than Windows there was a lot of discussion how compatible the new Delphi should have been with the current language, in terms of core language features and overall perception of the language. Eventually the decision that emerged was to do keep an extremely large degree of compatibility, with a few significant and bold steps towards a language more capable of attracting a new generation of developers.
What is Automatic Reference Counting?
(cross linked objects with weak references)
One of the changes in this space was the decision to embrace a new memory management model for the mobile platforms, following what Apple was already pushing as the iOS memory model: Automatic Reference Counting or ARC. On a memory-constrained device like a mobile phone using garbage collection is often considered an issue (as you end up consuming much more memory than strictly needed, and that also affects battery life) and ARC offers a simplified memory management model, easier to use in simple scenarios, fully deterministic, and robust.
That’s why Delphi picked the same model, extending the object reference counting model what has already been available for interfaces for a long time, making it more powerful with weak and unsafe references, and expanding it to plain object variables. This model does offer some significant advantages, and one of the original ideas was to expand it to the Delphi language on all platforms.
(entangled weak and strong references)
A few years after that planned was rolled out, we still see its advantages and benefits, but we have a much more clear picture of the drawbacks of the ARC model and what its effect will be in making it the default also on Windows and VCL applications.
We have long considered ARC memory management to be an improvement over the traditional Delphi model, as it removes and simplifies some of the memory management. However, we have learned when building and maintaining our large libraries and by talking with customers on complex code bases, that while ARC looks great on paper and for local variables and simpler scenarios, it often causes a lot of trouble for complex scenarios. Also the TComponent ownership model all of our libraries are centered on is at odds with ARC, making it complex to ARC-enable existing component libraries.
We could have decided to go “full ARC” also on Windows, but that would have caused a significant breakage of existing applications and components, causing far more trouble than the Unicode migration we did in the past -- which was due to external pressure of offering good international support and the fact that the underlying operating systems (starting with Windows) were also Unicode.
We had many requests to make ARC optional, but an ARC-enabled object won’t easily coexist with a non-ARC-enabled one, and we would need containers for both flavors of objects and would make it impossible to mix them. This would end up as an extremely complex scenario. Mixing ARC and non-ARC is not a viable solution, but also keeping different memory models on different platforms end up defeating the entire idea of “single source, multi platform” which is a core tenet of today’s Delphi focus both server side (Windows and Linux) and client site on all platforms you can build FireMonkey applications for.
Another significant angle is that ARC has a runtime cost. While things can be optimized and improved, at the end automatic management is not totally free. While Garbage Collection has a (very high) cost when it kicks in, ARC has a cost on objects lifetime and object interaction (unless you cautiously use const for all parameters passing). A positive effect of disabling ARC is a measurable performance improvement. Finally, ARC in Delphi is a bit at odds with the C++ side of the product, which is an integral part of RAD Studio.
The New Plan: Phasing Out ARC
(using interfaces in Delphi)
With all of these considerations in mind, and after extensive internal discussions, also involving partners and external developers, we have reached the conclusion that the best way forward is to move off the ARC model as a default memory management solution, and compensate for its loss with other alternatives.
In practical terms, the Linux 64-bit compiler in the 10.3 Rio release is going to offer the traditional Delphi memory management of the Windows platforms, making your Windows and Linux server side code totally equivalent -- at least in terms of Delphi language and memory management.
The go-forward plan is to not embrace ARC for the coming macOS 64-bit platform (so all desktop platforms are and will remain on the non-ARC model) and to disable ARC also for mobile platforms in the future (most likely around the next major release after 10.3). Notice that in 10.3 Rio the mobile compilers remain with ARC enabled, exactly like in 10.2.
What else for Modern Memory Management?
We still think reference counted memory management is relevant, but rather than introducing a new mechanism for it, we prefer to leverage and augment the existing reference counted model that is used by interfaces and strings in Delphi. This has been the case for a long time. Interface references and object references to the same object can cause trouble if not handled properly, but this is something most Delphi developers already know how to handle and there is a lot of documentation on the subject.
Interface references have been recently extended with weak references and even unsafe references. These additional options largely increase the power and flexibility of “ARC for interfaces”. In other words, while we are going to remove ARC for object references, ARC for interface references is a feature of the language that has been and will remain available.
On top of this, we want to improve and simplify the management of the lifetime (and memory management) of local short-lived objects with stack references. This is a not feature we are going to introduce in 10.3, but something we are actively investigating and can be partially implemented by developers leveraging managed records (a new language feature in 10.3).
(Memory leaks tracking made simple)
While we understand that this change to the plans made a few years back can come as a surprise and affect existing code, developers who were unhappy about the ARC model, supporting multiple models on different platforms, and its loss of performance compared to native memory management, are likely going appreciate the decision.
Offering alternative ways to further simplify Delphi memory management is in RAD Studio’s roadmap, but the current set of features (interfaces with reference counting and support for weak and unsafe references, the component ownership model, and the standard common practices) already provide a good framework to reduce the worry of manual memory management for Delphi developers.
Ultimately there is no perfect solution for memory management in programming languages, as automatic ones have their drawbacks, and manual ones are (well) manual. Delphi has had a very good balance in the past, which continues today and will only improve in the future.