Technology Programming

Performance Hit on Construction - 1

Dynamic Component Creation Gotcha (Don't Do This)

In the Dynamic Component Creation article we discussed the following code sample that demonstrated the wrong way to dynamically create and free a TComponent descendant that is only needed within a single method (created and freed in the same block of code).

In the article a warning was issued against dynamically creating components with valid owners and then explicitly freeing the new objects afterwards.

The following is an example of this (the TTable class is used for this example, but the issues raised in this article concern the dynamic creation of any TComponent descendant).

Looking at the code, the "Free" statement will always be called as long as the TTable creation is successful, which means the Create class method returns without raising an exception. Because of this, the TTable component, no matter what, will be alive (instantiated, assigned, and valid) only within the try/finally code block above.

Unfortunately, this "redundant safety net" can introduce the following undesirable side-effects:

Conflicting and Confusing Code

One problem with this code is that code readability suffers. The initial creation call where we pass "Self" as the owner conflicts with the Free method call in the Finally block.

Passing Self as the owner means that Self will destroy the TTable instance we just created. The last line of code (the call to "Free;") means the dynamically created TTable will not be destroyed by Self, but instead it will be destroyed programmatically.

This conflict in readability can increase the cost of maintaining the code.

Expensive Code Maintenance

When a maintenance programmer encounters this code, how is the problem fixed? Should the Free method be removed, or should nil be the owner? Is there another component somewhere that has overridden the Notification method intentionally and is waiting for this local TTable to be created? Is this really a problem? Should it be fixed, or left alone? What was the original programmer's intention?

The fact that maintenance programmers need to even ask these questions indicates a serious problem.

More problematic is that the answers to these questions can reside outside of the code sample presented here, requiring an intimate source-level understanding of every component in use on the form, as well as a good understanding of the context surrounding the code.

Performance Hit on Construction - 1

When our TTable is created, the Create constructor is called for TTable, and for each ancestor class that overrides the Create constructor, all the way to TComponent.Create. Here's the implementation for the TComponent.Create method:

constructor TComponent.Create(AOwner: TComponent) ;
begin
   FComponentStyle := [csInheritable];
   if AOwner <> nil then AOwner.InsertComponent(Self) ;
end;

Notice that if AOwner is nil, the Create constructor is extremely fast.

However, if AOwner is not nil, then AOwner's InsertComponent method is called. InsertComponent looks like this:

~~~~~~~~~~~~~~~~~~~~~~~~~
procedure TComponent.InsertComponent(AComponent: TComponent) ;
begin
   AComponent.ValidateContainer(Self) ;
   ValidateRename(AComponent, '', AComponent.FName) ;
   Insert(AComponent) ;
   AComponent.SetReference(True) ;
   if csDesigning in ComponentState then
     AComponent.SetDesigning(True) ;
   Notification(AComponent, opInsert) ;
end;
~~~~~~~~~~~~~~~~~~~~~~~~~

Note: A test program was created in Delphi to time the dynamic creation of 1000 components with varying initial component counts.

A number of methods are called here, but without a doubt the most expensive (in terms of having a negative impact on performance) is the call to Notification. Notification looks like this:

~~~~~~~~~~~~~~~~~~~~~~~~~
procedure TComponent.Notification(AComponent: TComponent; Operation: TOperation) ;
var
   j: Integer;
begin
   if (FFreeNotifies <> nil) and (Operation = opRemove) then
   begin
     FFreeNotifies.Remove(AComponent) ;
     if FFreeNotifies.Count = 0 then
     begin
       FFreeNotifies.Free;
       FFreeNotifies := nil;
     end;
   end;
   if FComponents <> nil then
     for j := 0 to FComponents.Count - 1 do
       TComponent(FComponents[j]).Notification(AComponent, Operation) ;
end;
~~~~~~~~~~~~~~~~~~~~~~~~~


This time-intensive code here is the last for-loop, which iterates through every single owned component, calling Notification again (it's a iterative call, so every component either owned or indirectly owned by the form (or whatever component was initially passed as the AOwner parameter), will have this method called. Additionally, the Notification method is virtual, so it can be overridden in descendant classes (and often is).

When the TTable instance is destroyed (the call to "Free" in the Finally block), ultimately the TComponent destructor is called (Free checks the instance to see if it's non-nil and then calls Destroy). TComponent.Destroy looks like this:

destructor TComponent.Destroy;
var
   j: Integer;
begin
   if FFreeNotifies <> nil then
   begin
     for j := 0 to FFreeNotifies.Count - 1 do
       TComponent(FFreeNotifies[j]).Notification(Self, opRemove) ;
     FFreeNotifies.Free;
     FFreeNotifies := nil;
   end;
   Destroying;
   DestroyComponents;
   if FOwner <> nil then FOwner.RemoveComponent(Self) ;
   inherited Destroy;
end;


Notice near the end of this method the check to see if "FOwner <> nil", followed by the call to the owner's RemoveComponent method. In other words, when this TTable instance was created, an owner was specified in the constructor, then we'll call that owner's RemoveComponent method later when the TTable instance is destroyed. RemoveComponent looks like this...

procedure TComponent.RemoveComponent(AComponent: TComponent) ;
begin
   Notification(AComponent, opRemove) ;
   AComponent.SetReference(False) ;
   Remove(AComponent) ;
   AComponent.SetDesigning(False) ;
   ValidateRename(AComponent, AComponent.FName, '') ;
end;

Notice at the beginning of this method the call to Notification (passing opRemove). Notification, as we've already seen above, is a iterative call and it's a virtual method.

So the additionally unnecessary performance hit gets you twice. Once on creation and once on destruction. This performance hit can be completely avoided by passing nil as the parameter to our TTable instance that is locally created and freed.
  1. When informed of this performance hit, it is sometimes suggested to use Application as the owner instead of Self (in the original code example "Self" was a TForm). This suggestion was based on the fact that statistically, the Application object will tend to own fewer components (e.g., just the auto-create forms), than a form would. While this may be true, passing the Application object as the owner actually has an even more severe impact on performance, because the Notification call iterates through each component owned by the owner. This means that every component on every form owned by the Application object will get it's Notification method called, in addition to every TComponent descendant owned by the Application directly.


When you specify an owner in the constructor of a TComponent, the owner's internal component list gains a new reference to your component. If the owner does not own any components, the list does not yet exist and is subsequently created. The amount of memory consumed here could easily be called negligible, but it is unnecessary all the same.

Notification is used for a number of purposes. One of these uses is auto-hookup. Auto-hookup occurs when a component that needs to link to another component overrides the Notification method, looking for a component of that type. When it's found (assuming the linked property is not already set), it connects automatically. A number of third-party component packages have these. Here's a variation of auto-hookup from the VCL (taken from DBTables.pas):

procedure TSession.Notification(AComponent: TComponent; Operation: TOperation) ;
begin
   inherited Notification(AComponent, Operation) ;
   if AutoSessionName and (Operation = opInsert) then
     if AComponent is TDBDataSet then
       TDBDataSet(AComponent).FSessionName := Self.SessionName
     else if AComponent is TDatabase then
       TDatabase(AComponent).FSession := Self;
end;

When "Self" is the owner, Delphi will pass your dynamically created component to every other component on the form, unnecessarily exposing your component to unexpected third-party auto-hookup code. This code can change your component's properties and assign handlers to it's events.

This code also introduces the risk of Delphi attempting to free the dynamically-created instance twice (this is a bad thing, and can result in AVs and other problems). This will happen if the owner is freed within the Try/Finally block.

This could happen unexpectedly if the code inside the Try block was time-intensive, and a method within the block directly or indirectly called Application.ProcessMessages.

If this condition existed, and the user closed the form while execution was in the Try block, then the form would be destroyed in the call to Application.ProcessMessages. When the form is destroyed, it also destroys all owned components (including the TTable). At this point in the execution, the reference created by "TTable.Create" is invalid. Any further references to that TTable object (e.g., calling methods or setting properties of TTable) would most likely result in access violations, as would the final call to Free. If this were to occur, it would be a problem that would be extremely difficult to find, debug, and ultimately trace back to the fact that you should have passed nil in as the owner to the dynamically created and locally-used TTable.

Summary

If you want to dynamically create a component and explicitly free it sometime later, always pass nil as the owner. Failure to do so can introduce unnecessary risk, as well as performance and code maintenance problems.
SHARE
RELATED POSTS on "Technology"
Blitzerwarner: Could You Escape the Speed Cameras?
Blitzerwarner: Could You Escape the Speed Cameras?
What Makes a Perfect Web Design Company?
What Makes a Perfect Web Design Company?
Inside No-Fuss Products In Gaming Computers
Inside No-Fuss Products In Gaming Computers
Gaming Institute, Game Design Schools at PAI-ILS, PAI International Learning Solutions Pune
Gaming Institute, Game Design Schools at PAI-ILS, PAI International Learning Solutions Pune
Free Music Players For Websites
Free Music Players For Websites
'See How NCP Tracks Down Fake Ration Card Holders'
'See How NCP Tracks Down Fake Ration Card Holders'
You Can Stop Panic Attacks in Just two Minutes
You Can Stop Panic Attacks in Just two Minutes
WordPress Blog to WP Site! A Journey to Online Success
WordPress Blog to WP Site! A Journey to Online Success
Videos will give your business portal an extra leverage
Videos will give your business portal an extra leverage
Excellent Ways To Make Weight Loss Work For You
Excellent Ways To Make Weight Loss Work For You
Five Notable Differences between National and International Logo Designs
Five Notable Differences between National and International Logo Designs
Who Should Your Phoenix Web Design Client Be?
Who Should Your Phoenix Web Design Client Be?
White Papers & Other Documents - including ALFLB
White Papers & Other Documents - including ALFLB
Google Analytics Tips
Google Analytics Tips
Want To Make Your Website Faster? Follow These Steps
Want To Make Your Website Faster? Follow These Steps
The Starting of Couture Apparel
The Starting of Couture Apparel
Choose Custom Website Design Services
Choose Custom Website Design Services
Choosing the Right Joomla CMS Developer
Choosing the Right Joomla CMS Developer
A Look at SQL Server Denali AlwaysOn
A Look at SQL Server Denali AlwaysOn
4 Tips to Choose the Perfect Flash Animation Course
4 Tips to Choose the Perfect Flash Animation Course

Leave Your Reply

*