Give in to the ARC side

Posted by on in Blogs
I thought I’d take a few moments (or more) to answer some questions regarding the just introduced Automatic Reference Counting (ARC) mechanism to Delphi on mobile platforms (namely iOS in XE4, and Android in a future release). Among some sectors of our customer base there seems to be some long-running discussions and kvetching about this change. I will also say that among many other sectors of our customer base, it’s been greeted with marked enthusiasm. Let’s start with some history…

Meet the new boss, same as the old boss.


For our long-time Delphi users, ARC is really nothing new. Beginning back in Delphi 2 (early 1996), the first Delphi release targeting Windows 32bit, the Delphi compiler has done ARC. Long-Strings, which broke free from the long standing Turbo Pascal limit of 255 characters, introduced the Delphi developer to the whole concept of ARC. Until then, strings (using the semi-reserved word “string”) had been declared and allocated “in-place” and were limited to a maximum of 255 characters. You could declare a string type with less than 255 characters by using the “string[<1-255>]” type syntax.

Strings changed from being allocated at compile time to a reference type that pointed to the actual string data in a heap-allocated structure. There are plenty of resources that explain how this works. For the purposes here, the heap-allocated data structure could be shared among many string variables by using a “reference-count” stored within that data structure. This indicated how many variables are pointing to that data. The compiler managed this by calling special RTL helper functions when variables are assigned or leave the current scope. Once the last string reference was removed, the actual heap-allocated structure could be returned to the free memory pool.

Hey this model works pretty well… Oh look! MS’ COM/OLE/ActiveX uses ARC too!


Delphi 3 was a seminal release in the pantheon of Delphi releases. For those who remember history, this is the first Delphi release in which the founder of Turbo Pascal, and the key architect of Delphi, Anders Hejlsberg, was no longer with the company. However, even with the departure of Anders, Delphi 3 saw the release of many new features. Packages and interfaces were major new language enhancements. Specifically interesting were interfaces, which placed support for COM/OLE/ActiveX-Style interfaces directly into the language.

The Delphi compiler already had support and logic for proper handling of an ARC type, strings, so it was an incremental step forward to add such support for interfaces. By adding interface ARC directly into the language, the Delphi developer was freed from the mundane, error-prone task of manually handling the reference counting for COM interfaces. The Delphi developer could focus on the actual business of using and accessing interfaces and COM/ActiveX objects without dealing with all those AddRef/Release calls. Eventually those poor C++ COM developers got a little help through the introduction of “smart pointers”… But for a while, the Delphi developers were able to quietly snicker at those folks working with COM in C++… oh ok, we still do…

Hey, let’s invite dynamic arrays to this party!


Introduced in Delphi 4, dynamic arrays freed developers from having to work with non-range-checked unbounded arrays or deal with the hassles of raw pointers. Sure there are those who get a clear kick out using raw memory locations and mud-wrestling and hog-tying some pointers… Dynamic arrays took a few pages from the string type playbook and applied it an array data type whose elements are now user-defined. Like strings, they too use the ARC model of memory management. This allows the developers to simply set their length and pass them around without worrying about who should manage their memory.

If ARC is good, Garbage Collection is sooo much better…(that’s a joke, son)


For a while, Delphi cruised along pretty well. There was Delphi 5, then a foray into the hot commodity of the time, Linux where the non-Delphi-named Kylix was developed. Delphi 6 unified the whole Windows and Linux experience… however not much really happened on the compiler front other than retargeting the x86 Delphi compiler to generate code fitting for Linux. By the time Delphi 7 was released, work on the Delphi compiler had shifted to targeting .NET (notice I have ignored that whole “oh look! didn’t Borland demonstrate a Delphi compiler targeting the Java VM?” thing… well… um… never-mind…). .NET was the culmination (up to that time) of the efforts of Anders Hejlsberg since he’d left for Microsoft several years prior.

Yes there has been a  lot of discussion over the years about whether or not MS really said that .NET was the “future” of the Windows API… From my recollection, I precisely remember that being explicitly stated from several sectors of the whole MS machine. Given the information at the time, it was clearly prudent for us to look into embracing the “brave new world” of .NET, it’s VM, and whole notion of garbage collection. We’re not mind readers, so regardless of any skepticism about this direction (oh and there was plenty of that), we needed to do something.

Since I have the benefit of 20/20 hindsight and having been a first-hand witness and participant in the whole move to .NET, there are some observations and lessons to be learned from that experience. I remember the whole foray into .NET as generating the most number of new and interesting language innovations and enhancements than I’ve seen since Delphi 2. A new compiler backend generating CIL (aka. MSIL), the introduction of class helpers which allow the injection of methods into the scope of an existing class (this was before C# “extension methods” which are very similar, if not more limited than helpers). I also think there were some missteps, which at the time, I know I vehemently defended. Honestly, I would probably have quite the heated discussion if the “me” of now were to ever meet the “me” of then Winking smile.

Rather than better embrace the .NET platform, we chose to mold that platform into Delphi’s image. This was reinforced by the clearly obvious link between the genesis behind Delphi itself and the genesis of .NET. They both were driven by the same key developer/architect. On several levels this only muddled things up. Rather than embracing GC (I have nothing really against GC, in fact, I find that it enables a lot of really interesting programming models), we chose to, more or less, hide it. This is where the me of now gets to ridicule the me of then.

Let’s look at one specific thing I feel we “got wrong”… Mapping the “Free” method to call IDisposable.Dispose. Yes, at the time this made sense, and I remember saying how great it was because though the magic of class helpers you can call “Free” on any .NET object from Delphi and it would “do the right thing.” Yes, of course it “did the right thing”, at the expense of holding the platform at arm’s length and never really embracing it. Cool your jets… Here, I’m using the term “platform” to refer more to the programming model, and not the built-in frameworks. Not the .NET platform as a whole…People still wrote (including us) their code as if that magic call to Free was still doing what it always has done.

The Free method was introduced onto TObject for the sole purpose of exception safety. It was intended to be used within object destructors. From a destructor, you never directly called Destroy on the instance reference, you would call Free which would check if the instance reference was non-nil and then call Destroy. This was done to simplify the component developer’s (and those that write their own class types) efforts by freeing (no pun intended) them from doing that nil check everywhere. We were very successful in driving home the whole create...try…finally…free coding pattern, which was done because of exceptions. However, that pattern really doesn’t need to use Free, it could have directly called Destroy.
Foo := TMyObj.Create;
try
{... work with Foo here}
finally
Foo.Free; {--- Foo.Destroy is just as valid here}
end;

The reason that Foo.Destroy is valid in this instance is because if an exception were raised during the create of the object, it would never enter the try..finally block. We know that if it enters the try..finally block the assignment to Foo has happened and Foo isn’t nil and is now referencing a valid instance (even if it were garbage or non-nil previously).

Under .NET, Free did no such thing as “free” memory… it may have caused the object to “dispose” of some resources because the object implemented the IDisposable interface pattern. We even went so far as to literally translate the destructor of declared classes in Delphi for .NET into the IDisposable pattern, even if all that destructor did was to call free other object instances, and did nothing with any other non-memory resource. IOW, under a GC environment it did a whole lot of nothing. This may sound like heresy to some, but this was a case where the power of the platform was sacrificed at the altar of full compatibility.

Come to the ARC side


What is different now? With XE4, we’ve introduced a Delphi compiler that directly targets the iOS platform and it’s ARM derivative processor. Along with this, ARC has now come to full fruition and is the default manner in which the lifetime of all object instances are managed. This means that all object references are tracked and accounted for. This also means that, like strings, once the number of references drop to 0, the object is fully cleaned up, destroyed and it’s memory is returned to the heap. You can read about this in detail in this whitepaper here.

If you ask many of my coworkers here, they’ll tell you that I will often say, “Names are important.” Names convey not only what something is, but in many cases what it does. In programming this is of paramount importance. The names you choose need to be concise, but also descriptive. Internal jargon shouldn’t be used, nor should some obscure abbreviation be used.

Since the focus of this piece (aside from the stroll down memory lane) is surrounding “Free”, let’s look at it. In this instance, Free is a verb. Free has become, unfortunately, synonymous with Destroy. However that’s not really it’s intent. It was, as stated above, was about writing exception-safe destructors of classes. Writing exception-safe code is also another topic deserving of its own treatment.

Rather than repeat the mistake in Delphi for .NET in how “Free” was handled, Free was simply removed. Yes, you read that right. Free has been removed. “What?” But my code compiles when I call Free! I see it in the RTL source! We know that there is a lot code out there that uses the proverbial create..try..finally..free pattern, so what is the deal!? When considering what to do here, we saw a couple of options. One was to break a lot of code out there and force folks to IFDEF their code, the other was to find a way to make that common pattern still do something reasonable. Let’s analyze that commonly implemented pattern using the example I showed above.

Foo is typically a local variable. The intent of the code above is to ensure that the Foo instance is properly cleaned up. Under ARC, we know that the compiler will ensure that will happen regardless. So what does Foo.Free actually do!? Rather than emit the well known “Unknown identifier” error message, the compiler simply generates code similar to what it will be automatically generated to clean up that instance. In simplistic terms, the Foo.Free; statement is translated to Foo := nil; which is then, later in the compile process, translated to an RTL call to drop the reference. This is, effectively, the same code the compiler will generate in the surrounding function’s epilogue. All this code has done is simply do what was going to happen anyway, just a little earlier. As long as no other references to Foo are taken (typically there isn’t even in Non-ARC code), the Foo.Free line will do exactly what the developer expects!

“But, but, but! wait! My code intended to Free, destroy, deallocate, etc… that instance! Now it’s not!” Are you sure? In all the code I’ve analyzed, which includes a lot of internal code, external open-source, even some massive customer projects used for testing and bug tracking, this pattern is not only common, it’s nearly ubiquitous. On that front we’ve succeed in “getting the word out” about the need for exception safety. If the transient local instance reference is the only reference, then ARC dictates that once this one and only one reference is gone, the object will be destroyed and de-allocated as expected. Semantically, your code remains functioning in the same manner as before.

To steal a line from The Matrix, “You have to realize the truth… there is no Free”.

Wait… but my class relies on the destructor running at that point!


Remember the discussion above about our handling of IDisposable under Delphi for .NET? We considered doing something similar… implement some well-known interface, place your disposal code in a Dispose method and then query for the interface and call if present. Yuck! That’s a lot of work to, essentially, duplicate what many folks already have in their destructors. What if you could force the execution of the destructor without actually returning the instance memory to the heap? Any reference to the instance would remain valid, but will be referencing a “disposed” instance (I coined the term a “zombie” instance… it’s essentially dead, but is still shambling around the heap). This is, essentially, the same model as the IDisposable pattern above, but you get it for “free” because you implemented a destructor. For ARC, a new method on TObject was introduced, called DisposeOf.

Why DisposeOf, and not simply Dispose? Well, we wanted to use the term Dispose, however, because Dispose is also a standard function there are some scoping conflicts with existing code. For instance, if you had a destructor that called the Dispose standard function on a typed pointer to release some memory allocated using the New() standard function, it would fail to compile because the “Dispose” method on TObject would be “closer in scope” than the globally scoped Dispose. Bummer… So DisposeOf it is.

We’ve found, in practice, and after looking at a lot of code (we do have many million lines of Delphi code at our disposal, and most of it isn’t ours), it became clear that the more deterministic nature of ARC vs. pure GC, the need to actively dispose of an instance on-demand was a mere fraction of the cases. In the vast majority (likely >90%) can simply let the system work. Especially in legacy code where the above discussed create..try..finally..free pattern is used. The need for calling DisposeOf explicitly is more the exception than the rule.

So what else does DisoseOf solve? It is very common among various Delphi frameworks (VCL and FireMonkey included), to place active notification or list management code within the constructor and destructor of a class. The Owner/Owned model of TComponent is a key example of such a design. In this case, the existing component framework design relies on many activities other than simple “resource management” to happen in the destructor.

TComponent.Notification() is a key example of such a thing. In this case, the proper way to “dispose” a component, is to use DisposeOf. A TComponent derivative isn’t usually a transient instance, rather it is a longer-lived object which is also surrounded by a whole system of other component instances that make up things such as forms, frames and datamodules. In this instance, use DisposeOf is appropriate.

For class instances that are used transiently, there is no need for any explicit management of the instance. The ARC system will handle it. Even if you have legacy code using the create..try..finally..free pattern, in the vast majority of cases you can leave that pattern in place and the code will continue to function as expected. If you wanted to write more ARC-aware code, you could remove the try..finally altogether and rely on the function epilogue to manage that instance.

Feel the power of the ARC side.


“Ok, so what is the point? So you removed Free, added DisposeOf… big deal. My code was working just fine so why not just keep the status quo?” Fair question. There is a multi-layered answer to that. One part of the answer involves the continuing evolution of the language. As new and more modern programming styles and techniques are introduced, there should be no reason the Delphi language cannot adopt such things as well. In many cases, these new techniques rely on the existence of some sort of automatic resource/memory management of class instances.

One such feature is operator overloading. By allowing operators on instances, the an expression may end up creating several “temporary” instances that are referenced by “temporary” compiler-created variables inaccessible to the developer. These “temporary” variable must be managed so that instances aren’t improperly “orphaned” causing a memory leak. Relying on the same management mechanism that all other instances use, keeps the compiler, the language, and the runtime clean and consistent.

As the language continues to evolve, we now have a more firm basis on which to build even more interesting functionality. Some things under consideration are enhancements such as fully “rooting” the type system. Under such a type system, all types are, effectively, objects which descend from a single root class. Some of this work is already under way, and some of the syntactical usages are even available today. The addition of “helper types” for non-structured types is intended to give the “feeling” of a rooted type system, where expressions such as “42.ToString();” is valid. When a fully rooted type system is introduced, such an expression will continue to work as expected, however, there will now be, effectively, a real class representing the “42” integer type. Fully rooting the type system will enable many other things that may not be obvious, such as making generics, and type constraints even more powerful.

Other possibilities include adding more “functional programming” or “declarative programming” elements to the language. LINQ is a prime example of functional and declarative programming elements added to an imperative language.

Another very common thing to do with a rooted type system is to actively move simple intrinsic type values to the heap using a concept of “boxing”. This entails actually allocating a “wrapper” instance, which happens to actually be the object that represents the intrinsic type. The “value” is assigned to this wrapper and now you can pass this reference around as any old object (usually as the “root” class, think TObject here). This allows any thing that can reference an object to also reference a simple type. “Unboxing” is the process by which this is reversed and the previously “boxed” value is extracted from the heap-allocated instance.

What ARC would enable here is that you can still interact with the “boxed” value as if it were still a mere value without worrying about who is managing the life-cycle of the instance on the heap. Once the value is “unboxed” and all references to the containing heap-instance are released, the memory is then returned to the heap for reuse.

The key thing to remember here: this is all possible with a natively compiled language without the need for any kind of virtual machine or runtime compilation. It is still strongly and statically typed. In short, the power and advantages gained by “giving in to the ARC side” will become more apparent as we continue to work on the Delphi language… and I’ve not even mentioned some of the things we’re working on for C++…


About
Gold User, Rank: 83, Points: 11

Comments

  • Guest
    Markus Joos Saturday, 15 June 2013

    Thanks Allen !
    I think, that's exactly what the community needs - discussing with someone who knows what he is talking about! Don't get discouraged by negative feedback. I for one really appreciate your explanations about WHY you have done things this or such way.

  • Guest
    Wouter Saturday, 15 June 2013

    Thanks, good read, and good explanation.

    I'm very happy to see that the language and the compiler keep evolving.

  • Guest
    Markus Saturday, 15 June 2013

    I also appreciate the explanations given and the discussion here. In order to be constructive there are some more things I'd like to know:

    1. there was talk about some algorithm which would do away
    with the problem of cycles in ARC without needing a
    [weak] declaration. I just don't remember who brought
    this up first, either Eric grange or Joseph Mitzen I
    think. Wouldn't using that one in the next version be
    helpful? (means deprecating [weak] already)

    2. What about optional support for ARC on the
    win32/win64/OSX platforms? Any plans for this one?
    And I'm specifically asking about optional support! ;-)

    3. What about this one?
    qc.embarcadero.com/wc/qcmain.aspx?d=116514
    It doesn't need to be the 1:1 implementation of this
    suggestion, but just giving us something to help find
    out which classes allocate non memory references in a
    way making calls to DisposeOf useful/necessary.
    Not everybody has the time to look through all those
    RTL/VCL/FMX classes to find out and for some 3rd party
    stuff there might not even be source code available.

  • Guest
    david berneda Saturday, 15 June 2013

    Thanks for this excellent article! One wish I had was something like an attribute or $opt- to skip ARC completely (for perf critical code) without [weak] overhead. (ie. in gpu canvas textlayout.render lists, generates lots of references)

  • Guest
    SilverWarior Sunday, 16 June 2013

    Hi!
    I'm a game developer myself. And for a game developer the code performance and low memory usage is of mmost importance.

    In games difference between one function taking 1ms or 2ms to compleete can mean difference between smooth working game and compleetely laggy game.

    Why?
    Quite often in my game I call certain functions for thousands of times per second so performance of this function has great impact on my whole game performance.

    So I'm wandering now how much would this Automatic Reference Counting affect my code if I create several local variables every time I execute theese functions.

    Now I asume that initializing any variables would now be a bit slower as it requires two steps (reservig memory, adding reference to this variable to some list) while now it only requires one step (reserving memory).

    And how about freeing these variables. Would be memory reserved for them freed up just after the function ends or on garbage colection intervals?

    Becouse if it is later I'm afraid it might increase overal memory usage of the application. And since in my case this happens about thousand times per second it could have quite big impact.

    Now I haven't got a chance to test something like this since I'm still focusing on Windows for the moment. I wanna develop my game to certain point before trying porting it to other platforms. But in the end I do plan of porting it to other platforms (athleast those supported by Delphi).


    You sad that you have milions of lines of code. How mich of theese code comes from games?

    Why am I asking this? As I mentioned at start of my post code performance is cruital in games.

    And somehow I have a fealing that most of you at Embarcadero doesn't realize the importance of the code performance othervise you would certainly go and fix many RTTI bugs or poor code which is causing poor performance. And yes this is something peepole are complaining for quite some time. Some developers even switched to other programing languages becouse of that.

    Now many of us game programers always folow this simply model of software development.
    - write some code
    - test it thourally
    - if there are any bugs fix them
    - write some more code
    - thourally test it
    - ....
    We rarely go and extend some existing code with new featires if we now there are bugs in ot or if its performance is slow.
    Why? Becouse we know that bugs or slow performance in base code will afect the extended code aswel. And performance in games is cruital.
    Also if you extend some code with bugs or poor performance before fixing it usually means that once you get to fixing it you may also be forced to reqrite all that code that extends this code.
    And that my felow programmers means more unnecessary work.


    As for Delphi programming language development:
    Including of other programming languages capabilities is a two-bladed sword.
    Upside is that doing so you may attract more programers who could benefit from theese features.
    Downside is that if you don't implement theese features perfecty you could quickly draw existing programers away from Delphi to other programing languages who have theese features implemented perfectly.

    If you ask me you should instad focus on implementing sone new and uniqe featires.
    For thisyou should atract as many new blod (newbie programmers) into our ranks. Why Newbie programmers?
    Nwebie programmers are still expirimenting wsith new approaches and are not only limited those already learned approaches so more chance for them to find something new and uniqe.


    Now for atracting new blod you will have to make Delphi even more accesible and especially compettive to other development tools.
    I understand that you can't afford to provide Delphi for free as does Microsoft its >Visual Studio becouse the Delphi is one of your major incomes.
    But still paying 200 € for Starter Edition seems to much to me (I own myself Delphi Proffesional).
    And yes I know about 30 day trial. But the fact is that in 30 days most pepole won't even manage to figure out if the programming is for them or not.

    I did managed to graps a hand on Delphi 6 at the time I started programming myself without any prior programming expirience except for one month agony of trying to anything more than "Hello world" applications in C++ (mostly due to inlogical C++ systax, athleast to me).
    I belive that the main reason why I managed to grasp the Delphi so quick is that somehow I always new that there is something more behind the visual look of the applications. I somehow understood how programs work without ever seing any code before. I know this might seem strange but that is the way it is.


    So if you ask me I would extend trial period for Delphi to athleast two or three months. Now you could limit trial licence to prevent of making any comercial software.
    If someone manages to make some comercially potentional software in les than three months I'm sure that person would be surely wiling to pay for Delphi as it's investment could easily be returned.

    Another thing I woul provide is free student licencess which could also prevent of making any comercial software.
    By doing this you could bring Delphi back into schools.

    And gor crist sake include the ability to compile 64 bit applications with Starter Edition.
    Nowadays in the era of 64 bit computers noone would be even willing to waste time on some development tool if he is not able to test 64 bit versions of their applications right away.


    And a few more suggestions of how to improve Delphi:

    Please do fix editor so that when I'm writing some code in the middle of the unit code blow which I got folded won't unfold when I do any sytactical error. Othervise code folding is practically unusefull.
    I think the best way to fix this is to limit error insight on checking only code withing current scope.
    If you need some more info on this idea contact me on my E-Mail and I'll try to explain it more thourally.

    Add capability to easily time-profile blocks of code. Right clicking or method guves you optiuon to select that method to be time-profiled.
    Delphi should automaticly add necessary code like TStopwatch.CreateNew and TStopwatch.Stop and this coude should not be visible to developer to prevent him from screwing it up by removing only part of it.
    This would also privide a great chance of excluding that profiling code from final builds.

    Please add capability for us to generate tree-like representation of our code so wee can quickly see dependancies between different code blocks.
    Also ability to generate flowcharts would be extreemly usefull.
    Right now I must use third party tools for this and most of them are not free. This increases the initial cost for developers.
    And since beginers developers would greatly benefit from them it might atract some more of them into using Delphi.
    Again if you want some more info en this ideas please do contact me.

    BTW Sorry for such huge post but this hass been coming now for some time.

  • Guest
    Alister Christie Sunday, 16 June 2013

    ARC is going to be a great addition to Delphi. Primarily because it is going to make code easier to write and easier to read, increasing developer productivity.

    Great post, very informative.

  • Guest
    Joseph Mitzen Sunday, 16 June 2013

    >Uh… they are… Even now. In Delphi. While not "objects" in
    >the same sense of using them to create instances, they >*are*
    >still objects in the sense that the act of declaring them
    >creates an "instance."

    Ok, I'll rephrase my request as "let's make types objects that we can actually use". :-) Or perhaps "let's make a type type".

    Someone was once working on a function in the forum which could take a string like "7 6.3 hello" and return the values according to user-specified types (or in this case, needing to pass in variables of the types desired). Being able to do something like
    ParseFunc("7 6.3 hello", Integer, Real, String) as can be done in some other languages where types are first-class objects would be very nice.
    Or something like

    theType := Real;
    x := theType(z);

    would also have a lot of uses.


    >there was talk about some algorithm which would do away
    >with the problem of cycles in ARC without needing a
    >[weak] declaration. I just don’t remember who brought
    >this up first, either Eric grange or Joseph Mitzen I
    >think.

    Eric uses this is DWScript and I noted it is currently used in Python.

    >Bacon describes a cycle-collection algorithm for reference
    >counting systems with some similarities to tracing
    >systems, including the same theoretical time bounds, but
    >that takes advantage of reference count information to run
    >much more quickly and with less cache damage. It's based
    >on the observation that an object cannot appear in a cycle
    >until its reference count is decremented to a nonzero
    >value. All objects which this occurs to are put on a roots
    >list, and then periodically the program searches through
    >the objects reachable from the roots for cycles. It knows
    >it has found a cycle when decrementing all the reference
    >counts on a cycle of references brings them all down to
    >zero. An enhanced version of this algorithm by Paz et
    >al. is able to run concurrently with other operations and
    >improve its efficiency by using the update coalescing
    >method of Levanoni and Petrank.
    http://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles

    And an ancient article (2000) about its use in Python that explains in easy-to-follow terms how the algorithm works:
    http://www.arctrix.com/nas/python/gc/


    >First of all, I find it interesting that folks immediately
    >jump in and start talking about what this or that is
    >*wrong* with what we’ve done. That’s certainly is no way
    >to start a conversation.

    Only true if it's open source. If it's commercial closed source, users are indeed permitted a sense of entitlement. :-) They've paid $1000+ for the privilege in this case. You can't say "patch submissions welcome" in response to a complaint because it's 1) closed source, and 2) you've already gone on record asking people not to submit patches with bug reports. ;-)
    Glory and little money in open source, money and little glory in closed source is the general rule of thumb.


    >All that does is make those areas that do need to be
    >addressed seem far more of a problem than they are in
    >practice.

    It's really a case of "the chickens coming home to roost". For many years whenever it was observed that development tool X had some feature that Delphi didn't, apologists/marketers/dev relations told users "But we're FASTER." Soon any feature anyone else had began to be viewed as performance-crippling among the true believers. Users who remained began to believe that saving nanoseconds was more important than anything, even cutting development time by weeks and if the product in question was a word processor (yes, someone did attack this example on the forum and try to convince me that performance-critical code sections of word processors exist). We weren't cheaper, or more popular, or quicker to write, but BY GOD WE WERE FASTER THAN ASSEMBLER!!! (in our own minds). We were told that managing memory manually made us smarter (or at least smugger) than our peers. Also, verbosity was (somehow) equal to clarity.
    Now, a large chunk of users are afraid that if Delphi does anything that any other language is doing that its performance will be destroyed (living in an era where interpreted and JIT are good enough for most tasks aside). It was the corporate owners of Delphi who instilled the "paranoid style" into the user base; now it's coming back to haunt you. You're trying to sell cross-platform to a remaining user base who shook their hands in rage and fury at mobile devices, OS X and Linux (except for a few years) and particularly web apps and screamed "Fat desktop Win32 applications FOREVER!!!" in order to remain loyal Delphi fans, just as they'd convinced themselves that 64bit and unicode weren't really necessary either (those who couldn't be convinced left, which makes the remaining user base really hard-core faithful).

    I'm watching the same thing happen all over again with the new marketing mantra that "non-native is evil". If and when you guys need to break that rule the hand-wringing, fear and rage will break out just like it is now. Become one of the last statically typed languages in existence to get around to implementing type inference and expect cries of "but declaring all of our variables at the top of every code block makes us the most easy to read language in the WHOLE WORLD!!!! NOOOO!!!!!", etc. Heck, I've seen people on the forum who say they still avoid STRINGS and use PChar instead wherever possible!!! This is not... normal, and you don't see it like this in other languages. This is clearly a result of short-sighted apologism and marketing. So don't complain about what you guys inspired among the faithful.

  • Guest
    Allen Bauer Sunday, 16 June 2013

    >Ok, I’ll rephrase my request as "let’s make types objects >that we can actually use". :-) Or perhaps "let’s make a type >type".

    Class references are "type types"... In a rooted type system, they will, by necessity, become even more fully featured.

    >theType := Real;
    >x := theType(z);

    What does that even mean? I don't know what you're trying to say here. Where would this be used?

    Your "ParseFunc()" example could be done with a rooted type system... it could also be done today with the use of the TypeInfo() standard function. Your example would be easier to accomplish with a rooted type system and be syntactically cleaner... however with a little bit of work, it could also be done with the language today (ARC or not).

    > Only true if it’s open source.

    I vehemently disagree with that assertion... So in your mind, only if it's open-source do people need to be civil and treat other developers with respect. Didn't know that... I just assumed civility and mutual respect were supposed to be universal.

    > 2) you’ve already gone on record asking people not to
    > submit patches with bug reports.

    What? Where did I say that? The closest I've ever come to saying anything remotely like that was when I expressed some frustration when *our* developers blindly apply some submitted patch without fully vetting it. I most assuredly never told customers to not submit patches.

    > For many years whenever it was observed that development
    > tool X had some feature that Delphi didn’t,
    > apologists/marketers/dev relations told users "But we’re
    > FASTER."

    What did you expect folks to say? You always talk about your strengths! Are you not doing the same thing by espousing the *strengths* of Python (which I really, like, BTW)? Criticizing a business for doing marketing is a little odd. I fully *admit* to doing that as well... Do you go into a prospective employer and immediately talk about all the things you failed at and are least qualified to do? You always build on your strengths, while at the same time work on minimizing the weaknesses.

    > I’m watching the same thing happen all over again with
    > the new marketing mantra that "non-native is evil".

    I can assure you that we've never done anything to characterize it in that manner... or is our stance on "native" code somehow threatening to your position on Python ;-) ?...

    We can play this game all day... but I will tell you that there is a place for native and a place for non-native JIT and interpreted. Those will forever be in tension over where that line is drawn. And that is a good thing, because it will make *both* styles better for having been at odds.

    From my perspective, this whole thing is rather interesting because for a while folks were saying "You guys need to add, this or that feature to the language!" or, even specifically, "When are we going to get LINQ in Delphi!?"... now we're actually looking at doing some of those things, and now we're getting "Oh... I didn't mean like *that*!" or "Why are you copying other language XX?!"... How are we supposed to respond?

  • Guest
    Eric Fleming Bonilha Sunday, 16 June 2013

    Allen
    I was against ARC before reading your post, but now I'm in favor of giving it a try. I just need to learn a little bit more of where should I use the [Weak] references, but your explanations are great

    You should write more often, I trully respect you and I personally like to read articles made by the master brains that actually make everything happen.

    Keep on the excellent work and never leave us!

  • Guest
    Eric Sunday, 16 June 2013

    >Uh, no.. Where did you miss that *most* code will not need to be changed?

    I did not miss your assertion, but that's to speak frankly, selling snake oil.

    You're well aware that there is a lot of customers that got stuck in pre-D2009 and never made the jump just to name one disruption. We're still occasionally catching Unicode glitches in our 1.5 million LOC codebase years after making the change, and I'm still having to rewrite the occasional slowpoke RTL Unicode implementations.

    > Were did you get this notion?

    You mean apart from looking at what was done in FMX and the RTL?

    > I take it you’ve actually used this and are working with it right now?

    Well, I've been using Pascal with ARC-based objects for some years (since the turn of the millenia). So there is indeed some history there, and through most of that particular history, performance wasn't the key issue, leaks were.

    > So, in other words, we didn’t do it the "way you would have" therefore it’s wrong. Got it. And Free was *not* renamed…

    No need to get so defensive, I merely restated what you wrote. In D.Net you used it everywhere as a default mechanism, resulting in widespread overhead (not that this overhead was a bottleneck in benchmarks, but I'll let that slide).

    > Eric, I really, really think you are blowing this way out of proportion.

    If you're looking at existing code-bases in the millions lines of code, the prospect of porting them with budgets on a shoe-string to end up with a still proprietary and not "state of the art" or mainstream tech, probable new bugs, with no new major features for the customers...

    No, that change (and other recent ones) don't have consequences that are blown out of proportions, they're EOL'ing Delphi here, it's as simple as that.

    > There are *some* of what you mention, but in my not so humble, extremely experienced opinion,
    > the benefits gained far outweigh any of the downsides that you mention.

    My point is that the benefits could be greater and the downsides non-existent or *really* corner-case. Just like in Python, which uses ARC, you have weak references but no one hardly ever uses them, they're there for corner-case and special optimizations, and not a key part of design.

    So invoking such minor potential performance losses as justification after the savaging the RTL got recently, or given the inefficiencies that can be found in current libraries such as FMX, LiveBindings or DataSnap... well, it's not very serious, is it?

    I mean if you want an instant performance boost on objects, have the compiler generate code for instance initialization and cleanup or managed fields, don't do that with RTTI! That issue has been known for years, it affects classes, records and dynamic arrays. That'll do far more for performance than tweaking side-aspects of instance destruction...

    Using RTTI for that made sense in the days when memory was limited, it's just like "dynamic" vs "virtual".

    > I have many other developers with whom I work on a daily basis and whose opinion I deeply respect.

    I don't know them, what they do, or their background. But it looks to me that they're either not much listened to, or not very performance-minded given what slipped in the RTL through the last years, from the various RTL de-tunings, TMonitor, or the libraries I cited above.

    Perhaps there is no budget (time and/or money) for performance implementations, and I would understand that, we all have that constraint, but then you wouldn't be writing that you haven't heard about issues, would you?

    > As far as a "global lock" in weak reference management…
    > there’s a bloody global lock when hitting the memory manager in many cases!

    Indeed, and that is what other have written about (for years now), ever heard of attempts to make FastMM for multi-thread friendly like SynScaleMM? Or the alternative MM that were in the FastCode Memory Manager challenge?

    FastMM was picked at the time because it had excellent performance on the hardware back then and the problems that were typically faced. It's multi-threading limitation was known, has been discussed, and since, solutions given.

    It's interesting that you mention it, I was hoping that EMBT would get the ball rolling and invest in it, but that wasn't the case, I even spotted the old Borland MM being used in some cases...

    So rather than coming up with it, you should be ashamed of having left this memory manager unchanged. At the time FastMM was a major instant boost, and FastCode did more for Delphi performance in the last decade than the compiler (or EMBT-created RTL) did... and what is a native compiler for if not performance?

    > Where did I "laugh at GC’s?"

    "that's a joke son" ?
    Maybe that's not what you meant, but that's how it sounded.

  • Guest
    Luigi Sandon Sunday, 16 June 2013

    What "zombie" instances are useful for? Frankly, I do not understand why instances that cannot be used anymore should stay in memory and not freed immediately. They can hold a lot of memory (depending on the class design), and IMHO they could also be a security issue and lead to some "use-after-free" (of better, "use-after-disposeof") vulnerabilities. Frankly I hope ARC will be only an option in Win32/64, or it will be a reason to move all of our server code to C/C++ (using GCC and VS, sorry, we abandoned C++ Builder years ago because of compatibility and performance). Maybe will keep Delphi only for GUIs.

  • Guest
    Nick Hodges Monday, 17 June 2013

    "snake oil"

    Oh please.

  • Guest
    Markus Monday, 17 June 2013

    Reply to Luigi:

    Zombie instances are not too useful. But they avoid that somebody else who didn't yet get the information that this instance is on longer valid tries to call something on it and get's an access violation or if that memory should be used by something else already even call errorneously something absolutely unwanted (a call to PostMessage(0, wm_quit, 0, 0);
    comes to mind... ;-) (not sure about the order of params out of my head)

  • Guest
    Luigi Sandon Monday, 17 June 2013

    But waht happens if I call a method of a zombie instance without checking first if it is? Is any call automatically checked, or not? I understand DisposeOf in the context of a GC, far less in the ARC context.

  • Guest
    David M Monday, 17 June 2013

    Allen: "As far as a "global lock" in weak reference management… there’s a bloody global lock when hitting the memory manager in many cases! ....

    "the best practice for doing large numbers of parallel tasks on sets of data is to ensure that all the memory needed is acquired upfront. Then dispatch different portions of that data to the various parallelized tasks. The more isolation among tasks, the far better your performance is going to be."

    Guenther Schoch: "Perhaps it would make sense to improve the memory manager as well for that area. The multicore story gets now more and more important to properly scale the performance."

    This is unannounced at the moment, but I am working on a new memory manager which does not have a global lock, and is designed for multithreaded usage, including cases where memory is allocated in one thread and freed in another, and many threads are allocating and freeing at once. It also uses a more secure design than FastMM4, which may be important for world-facing code, eg web servers. It's a personal project which I have not yet announced, but if you are interested (Allen, Guenther, others) please feel free to contact me at [email protected]

  • Guest
    SilverWarior Tuesday, 18 June 2013

    QUOTE: the best practice for doing large numbers of parallel tasks on sets of data is to ensure that all the memory needed is acquired upfront. Then dispatch different portions of that data to the various parallelized tasks. The more isolation among tasks, the far better your performance is going to be

    Do you realize that by doing this way you are increasing memory requirements of your application as you need to be duplicating some of the data.
    Do you think you would get much performance gain if that data is rapidly changing? No you won't! Why? Becouse everytime the data changes you will need to synchronize this change throughout your threads and by doing this you will be creating large data copying which would still suffer due to global memory lock.

  • Guest
    Allen Bauer Tuesday, 18 June 2013

    "Do you realize that by doing this way you are increasing memory requirements of your application as you need to be duplicating some of the data."

    Huh? That's not implied at all by what I said. I was talking about isolation between parallel tasks. If there are inter-dependencies between tasks, that is going to effect performance. The more you can isolate the tasks and their data, the better your performance. Yes, that does mean you sometimes gain performance at the expense of increased memory usage. That's *always* been a tradeoff and continues to be. As soon as you must "synchronize" data changes between parallel tasks, you've lost that isolation.

  • Guest
    Markus Tuesday, 18 June 2013

    What about the suggestion of getting rid of Weak by using the Bacon's algorithm described here?
    http://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles

  • Guest
    Markus Tuesday, 18 June 2013

    Some well known poster named Rudy just provided a link to some IBM research paper about that algorithm which might provide more detail about it:

    http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf

  • Guest
    Allen Bauer Tuesday, 18 June 2013

    re: Bacon.

    That's very interesting... Mainly because something very close to that algorithm has been bouncing around my head for the last few months. One key difference is when to place objects onto the "roots" list. I was looking at actually detecting real "roots" by where they are physically located in memory. This algorithm treats all non-zero decrements as potential "roots".

    Another interesting thing about this algorithm is the use of "color" to determine the state of the object. This is also done in the current ARC implementation... Objects are marked "white" when they are in the process of being freed (see __MarkDestroying()). Likewise, when objects are "Disposed" (see __SetDisposed) they are marked (loosely analogous to) "black". In fact, the algorithm indicates that the RC(T), Color(T), and Buffered(T) all occupy the same "word" in the object instance. The FRefCount field contains the destroying(T), disposed(T) and RC(T) flags, similarly.

    As I've already stated, this is certainly something I want to continue to look into...

  • Please login first in order for you to submit comments

Check out more tips and tricks in this development video: