Friday, May 29, 2009

Better smart-linking through class construction

Singletons, global services, and initialization. Most Delphi folks (myself included) don't really think about the impact of these things on how much code is being linked into their application. How many of you have your own stable of utility functions scattered throughout some number of units? You rely on these things everyday and would tend to feel naked without them. Even when you bang out that little test or utility app, those units are the first thing that goes into your uses clause. Have you ever stopped to think what impact they may be having the size of your little app? Long time Turbo Pascal and Delphi programmers have always taken for granted the smart-linker and its ability to ensure that only the code that was "touched" is what is actually linked into your final application. However, there are a lot of things we unwittingly do that can undermine the linker's "smartness." Try this little experiment; create a new console application, add all your favorite utility units to the uses clause and compile,  making sure building with packages is off. Now look at the size of your resulting executable. OMG! It does nothing, yet it is > xxxxK! Let's look at something we're working on to help you (and us) out.

As was mentioned at Delphi Live! several weeks ago, we're currently working on adding what we're calling "Mouse/Touch Gesture support" to VCL. This will eventually evolve into full multi-touch as the OS services and hardware becomes available. Initially we've been building our own "gesturing engine" and adding it throughout the VCL. From the outset, we knew that baking this support into the core of VCL was going to be key to its acceptance. At the same time we also know that there are many of you that don't need or want all that extra code slathered throughout core VCL. We also wanted to make sure this new gesturing support was "pluggable" at many levels. I'm not going to cover details about how this whole subsystem works, but simply want to use it as an example of something else we're looking at adding to the Delphi language.

Traditionally, if you had some set of classes that required some kind of global initialization or a class that was a singleton, you would place this startup code into the unit initialization section. Unfortunately, this has the side effect of dragging into the resulting binary all code and data that is directly and indirectly touched by the initialization code. Simply "using" the unit will drag in the code. Sometimes that is what you want, but what if all you wanted to do was use some other class or call some disconnected utility function within that unit? You have to pay the size and startup performance costs associated with initializing classes and data that you never even use in your own code.

Over the years, there are some "tricks" we've all played to try to get around this. The most common is in the case of singletons, where we "hide" the singleton instance behind a global function that will initialize the instance when it is first called. The problem is that everyplace you use the singleton, you incur a function call. Other tricks include injecting into each public method of an object, code that calls a helper method that does the global initialization. This also has the side-effect of incurring this call for each method. The interesting thing is that the compiler already has mechanisms in place to figure whether or not you're "touching" a given class or not. Why not just trigger on that and call an initialization method, if and only if that class is referenced someplace in code your. This really means whether or not the reference graph can trace all the way back to the main program block.

Enter class constructors. We're talking native Delphi here. Delphi for .NET and Delphi Prism have had class constructors all along since that is a concept that is baked into the .NET Common Language Runtime (CLR). However, there has never been the equivalent concept in Delphi for Win32. We're looking at fixing that. What if the following simple singleton pattern were possible?

unit SingletonUnit;

interface

type
TSingleton = class
private
class var FSingle: TSingleton;
class constructor Create;
public
procedure DoSomething;
class property Instance: TSingleton read FSingle;
end;

implementation

class constructor TSingleton.Create;
begin
FSingle := TSingleton.Create;
end;

procedure TSingleton.DoSomething;
begin
end;

end.

Now you can simply access the singleton by TSingleton.Instance.DoSomething; Because of the class constructor, the compiler will ensure that it is called long before you actually call "DoSomething" on the Instance class property. The astute among you have probably already spotted the hole in the above case. There's a memory leak now. How is the FSingle field destroyed? This is where the non-garbage-collected world of native Delphi has to do a little more work. The class destructor is how this will be handled.


  private
class var FSingle: TSingleton;
class constructor Create;
class destructor Destroy;
public
...
class destructor TSingleton.Destroy;
begin
FSingle.Free;
end;

Now let's look a little at how this all works. Class constructors (or type initializers) differ from instance constructors in that it is execute before any methods can be called or instances of the class constructed. The only rule is that it needs to run before anything else. How long before is largely irrelevant. Unlike class constructors in .NET, you cannot explicitly call them in Delphi Win32.  For Delphi Win32, class constructors are called in a manner similar to unit initialization. The difference is that the compiler treats them as a "weak reference." The class constructor is only explicitly called if other code references the class. Class constructors are called prior to the unit initialization getting called for the unit in which the class with the class constructor resides. If one class constructor references another class with a class constructor in the same unit, the compiler attempts to order the constructor calls such that deepest reference is called first. Beware of cycles! If you have several classes with class constructors within the same unit and they all refer to one-another, the ordering may be indeterminate and you may end up with strange results or even crashes. So be careful before you decide to add them to that huge morass of spaghetti code you inherited from your predecessor. For class destructors, they are called in the exact reverse order of the class constructors and are called after a given unit's finalization section is executed.


Back to the gesture engine. How does this all relate? When you drop a TGestureManager component onto a form, the designer/code manager adds an instance field of type TGesureManager to the designed form class. There is also a reference to the unit in which that component is declared added to the uses clause. The mere addition of the unit to the uses clause isn't what causes the gesture engine to be initialized, it is the component reference on the form itself that does this. If you removed the component from the form and the reference is deleted and left the uses clause reference, all that would be linked in would be a stub initialization/finalization from the given unit.


I'm already seeing several places throughout the Delphi RTL and VCL that would benefit from this. What if you wanted to use function from SysUtils, but did not want to incur the extra weight of full exception support? We may add a class constructor to the base Exception class that hook up the proper functions in System. There are probably many more places in the RTL and VCL that could be refactored in this manner. Of course we need to be cognizant of the fact that going too far may introduce strange ordering differences and could make some things behave badly or radically different. While this isn't some kind of magical "incredible shrinking machine," it is another tool that can be easily leveraged as we are adding and improving the VCL libraries. We will be able to add significant functionality without grossly impacting the minimum core set of code.

18 comments:

  1. Really nice Allen!. Just one thing comes to my mind, in my apps y frecuently use Dlls as plugins, with singleton objects shared between the main app and the plugins, for example the database connection object. Now I wonder how the memory manager will handle this if I change my singleton objects by this new singleton classes?, if the application.handle is shared, then the singletons classes will point to the same memory address?.

    Leonardo.

    ReplyDelete
  2. Interesting, classes seem much more complete now than they did a few versions ago, with every member that needs it now having a "class" equivalent.

    TMyClass = class
    const a = 10;
    type TMyEnum = (e1, e2, e3);
    {class} var
    Var1: Integer;
    Var2: Integer;
    {class} constructor Create;
    {class} destructor Destroy;
    {class} procedure Proc; {static;}
    {class} function Func: Integer; {static;}
    {class} property Prop: Integer read Var1;
    end;

    I can't think of a case where you'd need "class const" or "class type".

    ReplyDelete
  3. Really nice!
    Love it if you remove the need to avoid most Delphi librarys to not take the bloat hit when i just need something small from that unit.

    Keep that route of improving things and become open...
    Loved the lab videos.

    We need more infos/videos of the things youre "toying" around :)

    ReplyDelete
  4. @Clinton Johnson.
    Not everyone is sitting in a lazy old company that only develops Intranet/DVD stuff for goverment agencys, that are happy when the thing works somehow...

    The world is moving and also introducing new distribution channels, technologies and massive userbases...

    Don't look at your needs and say none is going to profit from a solution to codesize explosions.

    One aspect is true... the solution should not tamper with all code like many solutions they implemented to make Delphi more compatible with C++ Builder or include some multithreading locks and stuff.

    They should do it but implement it in a clean way and make a commandline switch for it.

    ReplyDelete
  5. I really really like new language features that would make my code cleaner, faster and smaller.
    But what would make my code even smaller and cleaner would be a more extensive VCL that would standardize common algorithms. For example in the current project I work on there are 3 different implementations of CRC32 and 2 different implementations of Blowfish encryption…

    Now regarding “class constructor”. What I think would be great addition is a compiler switch that adds automatic logging for each constructor/destructor/initialization/finalization call. The programmer can decide if the logging is done to a log file or whatever it attached to the IDE for online inspecting (There is a side benefit for Embarcadero – the log files could be used for unit testing in future version to see they have not changed the behavior of the initialization order).

    Thanks,
    Yoni.

    ReplyDelete
  6. Love it!

    @Clinton Johnson
    > I suspect the actually real world size benefits will be negligable for 99.9% of cases for 99.9% of people.
    Agreed. But you could say that of a lot of features and yet I don't want them removed. For example - how many projects replace the default callback functions that determine which exception classes are raised for external exceptions? Yet, when I needed it, I could replace those functions and get back meaningful error messages from non-Delphi code we use. So a seldom-used feature has become invaluable to us.

    Oh, and it saves you some time at startup too. That will likely be significant on a lot of large projects.

    ReplyDelete
  7. I recently bumped into this issue. I'm writing a program I want to keep as small as possible, as it will run permanently. There was a 'nice to have' feature, requiring clipboard support. But I ditched this feature, as soon as I saw the impact of adding the Clipbrd unit to the project, that's several 100 K.

    Only I'm not sure if your class constructors would easily solve this issue. Because I do want to use the clipboard singleton object, but only to put a bitmap on the Windows clipboard. It seems to me the required code size can't be more then a couple of KB. I guess the Clipbrd unit drags in a lot of unrelated code, but to prevent this, we would probably need more in the line of 'smart method pruning'.

    ReplyDelete
  8. Delphi Prism has something similar to this:

    TStatic = static class
    private
    FField: Integer;
    public
    constructor Create;
    destructor Destroy;
    end;

    Which translates to this:

    TStatic = class
    private
    class var FField: Integer;
    public
    class constructor Create;
    class destructor Destroy;
    end;

    Could we have this shortcut in Delphi for Win32, too?

    It makes implementing the Singleton pattern so much easier ... you don't even need to make an instance of the class which safes the additional bytes needed for Self. ;)

    ReplyDelete
  9. An already existing way of avoiding unwanted linking in spite of a (singleton) class initialization:

    "divide and conquer" :)

    Using 2 units (and optionally the project explorer convenience "directory ruled treview" to group those 2 units under a specific path) allows one to include the class unit at no cost. The Initiator unit have to be consciously included to let the "singleton magic" operate.

    unit MySingletonClass; // may be included everywhere

    Type
    TMySingletonClas = ... ;

    unit MySingletonClass_Initiator; // should be included only if required

    uses
    MySingletonClass;

    initialization
    // do initialize the singleton class here
    finalization
    // do free the singleton class here

    ReplyDelete
  10. You say "beware of cycles". Do you have any safeguards in place to keep circular dependencies from screwing things up? If you go and introduce the static initialization order fiasco into Delphi, I'll never forgive you, Allen...

    ReplyDelete
  11. @Dan Bartlett:
    >I can’t think of a case where you’d need "class const" or "class type".

    I can. I can think of plenty of uses for them, actually. Well, for class consts at least. More specifically, for virtual class consts.

    For example, if you have a large hierarchy of classes descended from one base class, and you want to put a unique tag on each class, (very useful as a sanity check for serialization purposes,) right now you need to write that as a virtual function that returns a hard-coded literal value. It would be a lot nicer to just put that literal value directly into the VMT as a virtual class constant.

    ReplyDelete
  12. >I can’t think of a case where you’d need "class const" or "class type".

    class const:
    I know lots of "global constants" that actually belong to a certain class. Declaring them globally just clutters the name space but doesn't offer any additional functionality. It's also a good practice to hide this stuff (private, protected) if it's not really required outside of the class.

    class type:
    This is less common, but I think everybody knows some types that internally store data in a record/object. This data object is not used outside of the class, but you need to declare it globally as you reference to it (Yes, you can declare it loacally and use Pointer/TObject in the interface. Good bye type safety and Happy Casting is all I say...).
    Class Types are a much cleaner way to ensure that this type is available at declaration level but still hidden from the rest of the world. (You can even declare it protected and allow descendents of your class to use it as well...)

    ReplyDelete
  13. [...] constructors. I know Alan mentioned them after Delphi Live but I didn’t see any mention about them in the recent flurry of information. These are really [...]

    ReplyDelete
  14. TSingleton's instance constructor have to be a private member too. The only problem is that Delphi compiler is not able to hide the instance constructor, therefore you can create an instance like this:

    var
    S: TSingleton;
    begin
    S := TSingleton.Create;
    ...

    Unfortunately, this is an incorrect behavior.

    ReplyDelete
  15. TSingleton's instance constructor has to be a private member too. The only problem is that Delphi compiler is not able to hide instance constructors, therefore this singleton behavior is avoidable in the above code.

    ReplyDelete
  16. [...] T0 use extended RTTI, you need a TRttiContext.  It’s an interface to a global RTTI object pool, and you only need one of them. So it makes sense to attach it at the class level instead of to each individual object.  So as long as we’re doing this the Delphi 2010 way, let’s put it in a class constructor. [...]

    ReplyDelete
  17. Then you have winter coats that are greater for individuals snowy windy days at the hockey rink. And then you have ski jackets which are complete other sub-genre of winter season coats. Do not even get me commenced on womens coats. moncler jackets women sale And then there’s the numerous assortment of winter coat manufacturers There’s MEC, Canada Goose, North Confront, Sierra Patterns, Columbia, Spy, Couloir, Keely, and a lot of several more.So you want to be warm this winter… and you have heard parkas are very hot even in arctic temperatures… but what’s a parka? Initially, parkas had been worn by the Inuit and other Arctic peoples.Moncler Down Jackets The regular parka layout is truly “a pullover product outer garment created from caribou skin having a fur lining close to the hood”. The idea getting that if animals could stay warm, individuals could put on their skins and remain sizzling also, even in most likely the most inhospitable situations.

    ReplyDelete
  18. Actually, in Delphi XE, using class destructor for TGestureManager causes an access violation in TGestureManager.Destroy, as FInstanceList is freed in Class Destructor, which is called before component destructor.

    Refer to GestureMgr.pas, line 651.

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.