Handling/Releasing Objects

Currently, I have a setup with an object hierarchy similar to below:

Parent Object
   --- ChildObject1
   --- ChildObject2
          --- GrandChild1
          --- GrandChild2
   --- ChildObject3


Now, there are only about 6 child objects, but several of those child objects can contain 60-100 granchild objects, of which, there are some that are arrays (one is 1000 in length).  

Originally, I had a main UI form, which I would create multiple instances of, and it, essentially, played the role of the Parent object, with the child objects as properties.  The application is MDI in nature, and when I unload the project, I unload all open forms and set them to Nothing, and in this main UI form's unload event, I set each child object to Nothing.  I understand there would be some delay in releasing this memory...not a problem.

However, I wanted to move away from the current implementation to make it more general, and create a new class to act as the Parent object.  So I have a public variable declared as this parent object, and when the program ends, I set it to nothing.  I have tried arrays, the collection object, and the dictionary object.  All are ridiculously slow in loading and unloading these objects.

1) Why is this so?
2) What is different from destroying them from the form's unload event and otherwise?
3) What workaround is available?

Essentially what I need is a super fast collection type class to act as my Parent, and the ability to create and destroy objects quickly.  Hopefully someone has attacked this issue before and has a solution readily available.
LVL 28
Who is Participating?
Loading your 'Quick View' (it's like property window in VB) can be very fast, since there is only one edit control active, but specialized 'Tabbed Dialog' is easier/better for data entry.

I would suggest to use non-object version of Case class, and to have small UI wrapper objects to support Min/ Max/ other validation.
I use that technique (it's called "multicasting") to attach controls to business class properties, and it allows relatively easy changes (e.g. adding new field)
I use 12 different wrappers for data entry.  They can do some nice tasks just by setting a property.
- when value change, they change forecolor to vbHighlight (blue)
- when value is invalid, forecolor is set to vbRed and tooltip is set to error text

All control wrappers support these properties/methods:
Option Explicit
Public Name As String    ' used to associate it with business class property
Public Value As Variant
Public TabNo As Integer  ' 0 - control is not on Tabstrip, 1 - on first Tab, ...
Public Required As Boolean
Public ReadOnly As Boolean
Public WriteOnce As Boolean
Public Enabled As Boolean
Public ErrorText As String

Public Sub SetFocus()
End Sub

Public Sub Clear()
End Sub

Public Sub Init(Name As String, BOFieldName As String, TabNo As Integer, _
    Required As Boolean, ReadOnly As Boolean, Optional WriteOnce As Boolean)
End Sub

To fill form controls a simple loop is used:

        For Each x In colw
            x.Value = CallByName(m_Case, x.Name, VbGet)

To update object:

        For Each x In colw
            If Len(x.ErrorText) Then
                If x.Tabno > 0 Then Set tbs.SelectedItem = tbs.Tabs(x.Tabno)
                MsgBox x.ErrorText
                GoTo UpdateRecord_Exit
            End If
            CallByName m_Case, x.Name, VbLet, x.Value  ' update m_Case object

It is also easy to Enable or Clear all controls.

When new field is added:
- a Property Get/Let is added to business class
- in Form: few controls (e.g. label and textbox) are added, and code is added only in Form_Load:

    Dim wCaseName As cwSimpleText
    Set wCaseName = New cwSimpleText
    wCaseName.Init "CaseName", 1, True, False
    wCaseName.UpperCase = True
    Set wCaseName.TextBox = Me.txtCaseName
    colw.Add wCaseName, "CaseName"
    Dim wInputData7 As cwNumeric
    Set wInputData7 = New cwNumeric
    wInputData7.Init "InputData7", 1, False, False
    Set wInputData7.TextBox = Me.txtInputData7
    Set wInputData7.VScrollBar = Me.vsbInputData7
    wInputData7.Increment = 0.1
    wInputData7.Min = 0
    wInputData7.Max = 100
    colw.Add wInputData7, "InputData7"

Actually, there are few exceptions (e.g. cwButtonLookup handles two properties, and adds some code to form).  And I left too many things late bound... it works OK that way.
What is "ridiculously slow?"
When you used collections, what method did you use for destroying them? (iterating forward is slow, backwards is faster, setting collection = nothing is fast.)
Is your hard drive thrashing?
At what level have you placed the code to set objects to nothing (are you going through many "dot levels" to set a grandchild to nothing?  If so, try doing it from the child level.)
What do these objects contain?
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

VB's collection is slow if you have many thousands of items.
I used F. Balena's CollectionEx class - it was faster, and I needed .Exists method (it can be emulated with VB collection + error handling, but it's slow)

Few days ago I converted that back to normal collection, for future compatibility, i.e. easier conversion to VB.NET

I am not sure if it's good time to create your reusable class, but, here is some hardcore code - faster than collection:

both articles have performance results at the bottom, and code is a stack class, I think.  I didn't test it - it's a bit too hardcore for me  ;-)
AzraSoundAuthor Commented:
>>What is "ridiculously slow?"
We are talking in the order of 30+ seconds on a 400 MHz machine.  I had tried just about every method for destroying the collection, setting it to Nothing, setting individual items to Nothing, etc.  I tried just about every method for collections, dictionaries, and arrays, and all produced poor results.

>>What do these objects contain?
Mostly just numerical data, and some strings.

The code for setting self contained objects to Nothing is placed in each Class' Terminate event.

>>here is some hardcore code - faster than collection
The results look promising, though it will take a bit of time to weed through the code to understand it.
AzraSoundAuthor Commented:
A brief look at IMalloc and it seems it may be too difficult to implement due to the amount of variant length strings the classes use.  It appears it would have been the prime choice given the performance times offered in that chart.  I'll do some reading on using IUknown and the AddRef and Release methods...

>>I used F. Balena's CollectionEx class - it was faster, and I needed .Exists method

Where can I get a copy of this?  And just how much faster did it prove to be?
I cannot find any link to vbpj0898.zip

With 10000 items
 add 4 x faster
 item 5 x faster
 item by index 100 x faster
 remove was very slow
AzraSoundAuthor Commented:
Well, initial tests look very positive using IUknown and the AddRef/Release methods.  Running the same test case using this method has improved performance dramatically.  Prior to the alteration, as mentioned above, it took 30 seconds for the app to close.  Just ran it and it closed in less than a second.  Very nice...

However, this method just uses pointers to objects and retrieves them that way.  This class was built to receive events from its child class, which, it can now no longer do (at least using WithEvents), since all we're using are pointers.  I know I can get around this with a circular reference, but that seems very ugly, and may cause problems with the reference counting scheme now in place.

I'll keep you guys up to date on my progress, and please, continue to offer suggestions as they come to you...
I would suggest you to take a workaround. I don't know exactly what you are doing, but multithreading should be a good solution to make your application run faster.

The workaround is to build your hierarchical objects to an AxtiveX EXE project. In your application, you use createobject or new method to get reference to the parent object. With the public functions provided by the ActiveX Components, you can create child and grandchild objects. When you've done your work, set the reference to nothing. Since the ActiveX EXE runs in his own process and it takes care of releasing the memory, it will take no second to unload the forms of your application. The releasing is done behind the scene, so your users will not even notice it.

Anthony PerkinsCommented:

>> I cannot find any link to vbpj0898.zip<<
Try  http://www.vbpj.com/code/1998/vbpj0898.zip

Anthony, thanks
Balena's collection is inside that zip, cb0898.zip
AzraSoundAuthor Commented:
Well the fact that the remove was slow leads me to want to stay with using the IUknown approach (especially after spending so many hours rewriting code).

The ActiveX exe solution sounds somewhat appealing, aside from the fact that it still doesnt resolve the problem of releasing the memory quickly.  If I still maintained a collection or array or dictionary with that approach, it would still mean very slow release of all the memory used (on slower machines, which many clients will have, in excess of a minute).  Even though it may "appear" it isnt causing my application to hang, its still a huge lag on the machine as it eats up all of the CPU resources to destroy all of the objects.
The ActiveX EXE solution doesn't mean that the AxtiveX EXE component has to run in client machines. When you use createobject method, you can add a parameter with a host name so that the EXE will run on a remote faster computer. And it's also possible to share some public objects your clients all use.

First of all, you have to get a faster algorithm to destroy the memory. But as a workaround, ActiveX EXE is also very considerable.
AzraSoundAuthor Commented:
>>doesn't mean that the AxtiveX EXE component has to run in client machines
Considering this is a desktop application, I think it does.

>>First of all, you have to get a faster algorithm to destroy the memory
Algorithm?  I'm listening...
>>"Now, there are only about 6 child objects, but several of those child objects can contain 60-100 granchild
objects, of which, there are some that are arrays (one is 1000 in length)."

Do you use 2D arrays by any chance? I had a question that was similer in nature and discovered that the time to release memory is affected by how the array was declared. It was ameba that pointed this out and provided this example to demonstrate:

Releasing takes ~20 seconds for 5K rows on Win95, mainly because of its bad memory handling.
NT / Win2K is better.

' Fast sample (2.5 seconds)
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private arrData() As String

Private Sub Form_Load()
  Dim i As Long, j As Long, maxrows As Long
  maxrows = 5000
  ReDim arrData(1 To 39, 1 To maxrows)
  ' fill 2D array with data
  For i = 1 To 5000
      For j = 1 To 39
          arrData(j, i) = "s" & j
End Sub

Private Sub Form_Unload(Cancel As Integer)
   Dim tim0 As Long
   tim0 = GetTickCount
   Erase arrData ' deallocate array
   MsgBox (GetTickCount - tim0) / 1000 & " seconds"
End Sub

Now, replace i and j, and it'll be 45 seconds on Win95:

Private Sub Form_Load()
  Dim i As Long, j As Long, maxrows As Long
  maxrows = 5000
  ReDim arrData(1 To maxrows, 1 To 39)
  ' fill 2D array with data
  For i = 1 To 5000
      For j = 1 To 39
          arrData(i, j) = "s" & j
End Sub
Yes, in VB, we really have only 1D arrays efficient.

Also, if you Redim your array too often, it can a pain to release all those 'pages' instead of releasing few big memory blocks.

My suggestion is:

- Forget all those performance issues, use VB's collection or array
- Create proper object design (e.g. clsTree and clsNode classes, define all methods/properties you'll need)
- Test with e.g. 6 * 100 nodes, make each Node hold Variant (e.g. Array with 1000 Longs)
I am sure it can be pretty optimized (experts can help), just by creating right methods/properties.

Ref.: Speed Up Your Apps With Data Structures

If it is still slow, THEN replace collection with something else (CSuperCollection).
Tony, I'm not sure about side effects, but did you try

CopyMemory ObjPtr(cMyClass),0&,4&

Or, may be smth like this - clear process memory usage?

Function ClearProcessMemUsage(hProcess As Long) As Long
    Dim lpMem&
    Dim lPrivateBytes&
    Dim ret&
    Dim si As SYSTEM_INFO
    Dim lLenMbi&
    Dim memLow As Long, memHigh As Long
    GetProcessMemUsage = -1
    lLenMbi = Len(mbi)
    Call GetSystemInfo(si)
    lpMem = si.lpMinimumApplicationAddress
    While lpMem < si.lpMaximumApplicationAddress
        mbi.RegionSize = 0
        ret = VirtualQueryEx(hProcess, lpMem, mbi, lLenMbi)
        If ret = lLenMbi Then
            If ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT)) Then ' this block is In use by this process
               lPrivateBytes = lPrivateBytes + mbi.RegionSize
               ZeroMemory lpMem, mbi.RegionSize
            End If
            On Error GoTo Finished
            lpMem = mbi.BaseAddress + mbi.RegionSize
            On Error GoTo 0
            Exit Function
        End If
    ClearProcessMemUsage = lPrivateBytes
End Function

PS Not tested!
That will overwrite the memory, but does it actually deallocate it and free it to the OS, or would it result in a memory leak.
Paul you're right
May be use VirtualFree lpMem, mbi.RegionSize, MEM_DECOMMIT?

AzraSoundAuthor Commented:
I'd be happy to use something like that Ark, if, as Paul mentioned, we can avoid potentially harmful side effects.
AzraSoundAuthor Commented:
There are only 1D arrays, but thanks for that.

I agree that perhaps a small redesign would improve performance.  The last node in the model could probably be converted to a UDT instead of a class, but was created as a class initially to allow for automation and reduce, drastically, the amount of initial code required.  However, this is where a lot of the overhead is coming from when clearing memory, and in the long run, we will more than likely try to move to that direction.  To get the app up-and-running to start testing our calculations and getting some data input/output as soon as possible, we opted for this current model.  I suppose I was hoping to find a way to manage the memory better with the current setup, to keep me from having to rewrite so much of the other code that would need to be altered otherwise.
AzraSoundAuthor Commented:
Ok, I am interested in hearing ideas on how some of you would implement the object model to fit this design:

1) The application runs on a desktop/laptop
2) Each beginning of data entry can be thought of as a Session
3) Each Session can contain up to 10 Cases
4) Each Case is unique based on its CaseName
5) Each Case must maintain information about InputData, OutputData, and properties for two additional pieces of equipment (stored in a database)
6) Certain information must be maintained for the InputData and OutputData (numerical data only):
     a) Actual Data Value (varies on units selection)
     b) Maximum and Minimum values (varies on units selection)
     c) The data's specified units for English, Metric, and Canadian
     d) Tie in current units selection to maintain proper data/max/min values
7) There are 90 pieces of InputData.  3 of which are arrays containing 1000 elements each.
8) There are 180 pieces of OutputData.  7 of which are arrays containing 1000 elements each.  Another 7 of which that contain 600 elements each.

The main goals:
1) Efficiency (runs as quickly as possible, even on slow machines)
2) Scalability (new InputData and OutputData can be added without too much code rewrite/alteration) and Extensibility (to add onto the object model as easily as possible in the future)

Youve got an idea of what I had thrown together initially.  Everything seemed ok except performance.  At the current stage, a new object model can still be implemented, so I'm open to your ideas.
I can guess some classes, but this is still a bit too abstract for me.

You are saying this is data entry application.  OK.  But I don't understand its DOMAIN (insurance, banking, manufacturing, secret weapons testing ...) -
you also mentioned compiler design in one old "Tree design" question (qid=20107780).  Is this connected?

Is data entry done by some machines?  In OO terms, who is the ACTOR?

> 4) Each Case is unique based on its CaseName

Does this mean:
You have different DATA ENTRY SCREENS (like forms, or subforms), e.g.:
- sb_SimpleCase (or ctlSimpleCase, or frmSimpleCase) with 10 InputData and 10 OutputData
- then sb_Case2 with different number of InputData, and different 'rules', e.g. different Max
- they ALL have exactly the same number of entries (90 pieces of InputData + 180 pieces of OutputData)

Can we say InputData and OutputData are different DATA ENTRY FIELDS (like TextBoxes)?
And you have different DataEntryField Types:
- df_Simple (one number, mapped to a simple TextBox)
- df_Array600 (array of numbers, e.g. mapped to a Multiline TextBox)
- df_Array1000

> 6) Certain information must be maintained for the InputData and OutputData (numerical data only):
>     a) Actual Data Value (varies on units selection)
>     b) Maximum and Minimum values (varies on units selection)
>     c) The data's specified units for English, Metric, and Canadian
>     d) Tie in current units selection to maintain proper data/max/min values

- I don't understand  "varies on units selection"
- I guess you are saving to database only a) Actual Data Value  ???
  and b), c) are 'rules', defined in your app

Object Interaction design / user interface

I guess, you'll have scenario ('use case') named "Add Data", where user will first select 'Case Type' (from combo, or from treeview), and then he/she will start entering data.

>to add onto the object model as easily as possible in the future
>new InputData and OutputData can be added without too much code rewrite/alteration

Nice thing to discuss!

If new field is added to table,
if it shows in a 'list', I have to:
- ...
if user can edit that field, I must:
- ...
AzraSoundAuthor Commented:
More clarification:

The company provides a predictive program for pumping unit design (those things out in the fields that pump oil).  They have complicated algorithms for determining the pumping unit's efficiency, cost, maximum output etc.  These values are calculated based on all of the input data (generally a combination of data about the unit itself, the motor to run the unit, other factors).  This is the InputData.

They can enter all of this initial data, call it the "BASE CASE", and then create copies of this base case (up to 9 copies for a total of 10).  They can then make small changes to the input data of these separate cases to see how the result may be affected by altering small bits of the input.  Only one case is viewed at a time, the idea being, to select the case from a dropdown or treeview type interface, and a form displays all of the information for that particular case.

This is an international product.  In some places, they enter data in Metric units, and others, based on the Non-metric standards (e.g., lbs to kg, ft to m).  Canadians use a mixture of both systems (e.g., they use Metric for distance but Non-metric for weight).  The interface is setup to respond to, and display, properly based upon the user's preferences. (Units and interface/data values can be altered at run-time as well)

InputData can be saved to a file for retrieval later.  All the cases are saved to a single file, which is currently implemented with an XML layout, e.g.,


The user then runs the inputted data into the calculations to receive all of the OutputData.  They can run a single case, or all cases at once to see data comparisons amongst the different input data.  This output data is used to generate reports and graphs.
I see.  Thanks for the details.

Basically, there is only one 'domain' object:

"Case" class
Public Properties:
    CaseName As String
    InputData (87) As Single
    InputDataArr1000 (3) As Variant
    OutputData (164) As Single
    OutputDataArr1000 (7) As Variant
    OutputDataArr600 (7) As Variant

    Recalc  ' maths, FEM, thermodynamics

There is no need for Session class, you can have a single function to read XML file and return array or collection of 1-10 "Case" objects...
And those Min/Max properties can be defined in some helper classes, or in Form_Load...

I think it is OK to have one form represent one Case object.

Child Objects from your Tree (ChildObject1, ChildObject2) represent PARTS of one Case object, correct?  They are PAGES of fields (your Form has ~6 Pages?)
User is selecting Page by clicking on Tabstrip or something like that...

While re-reading to understand it all...

> The application is MDI in nature, and when I unload the project, I unload all open forms and set them to Nothing, and in this main UI form's unload event, I set each child object to Nothing

I think your 30 seconds is NOT spent for destroying the collection, but for unloading the form loaded with many controls, or few heavy controls or usercontrols.
Or, if you loaded many controls dinamically, unloading will be very slow.

Maybe you can make a screenshot of your MDIChild form (1 or 2 'pages')...
AzraSoundAuthor Commented:
Yes you are correct, the child form that displays the data consists of a tabbed dialog consisting of 6 or 7 tabs.  Your idea of the structure is one I have reluctantly felt I may need to resort to.  Initially, everything was as automated as possible, and I was willing to take a hit on run-time performance because I knew it would be negligible.  However, I did not foresee having this much trouble unloading objects (my fault for not taking the time to see just how much output data they were going to generate).

The slow unloading is definitely due to the inputdata and outputdata objects, as each piece of data was, itself, an object.  When dealing with 5 or less cases, the unload time is not too noticeable, and generally speaking, most users will never create more than this many cases.  However, those that do would suffer greatly with 5+ cases and needing to unload tens of thousands of objects.  I had made each piece of data "self-describing", e.g., each was an object with properties:

Maximum, Minimum, EnglishUnits, MetricUnits, CanadianUnits, etc.

I could use XML file to describe the data and read it in:
For Each xmlNode In xmlFile
   CallByName(DataObject, xmlNode.nodeName, VBLet, xmlNode.Text)

I used the same logic with testing min and max values, and converting units and the data's value at run-time.  Everything could be wrapped up in a loop.  Of course, performance was a bit slower than hardcoding each item, but it wasnt significant.

In an ideal world, everyone runs an 800MHz machine or higher and I would be happy.  Til then, I guess I'm stuck hardcoding all of this stuff into the UI.

No harm done though, the automated system allowed us to get a base framework setup very quickly to run the calculations and ensure their accuacy.  Now that has been done, I can alter how those calculations are fed the needed data.
Maybe you can use only one form, instead of loading 9 forms at the same time.
When user selects some Case from the list on the left, show its data, e.g.:

You'll have 9 Case objects loaded, but without Min/Max/validation subobjects - they will be loaded just once in Form_Load.

To allow viewing 2 Cases at the same time, provide "New Window" menu - it will shell new app instance.
AzraSoundAuthor Commented:
Actually that was my current setup, using a single form, handling the cases in memory, and displaying the data for the selected case on the form.  I will put some screenshots up once my web server gets back online.

In the meantime, I want to be sure that doing this rewrite will gaurantee a significant improvement in performance.  The slow termination of the objects is definitely the issue, with or without a collection, dictionary, or arrays.  Will the clean up of memory be drastically improved simply by altering the input and ouput data to be of type single/double instead of as objects?  If the improvement is not super signficant, I may reconsider the move.
AzraSoundAuthor Commented:
AzraSoundAuthor Commented:
I am somewhat astonished that VB is so slow to unload objects from memory.  I created a new project and added my classes to it and changed all of their types to non-objects.  The time to unload a collection of 10 cases was nil.  I guess if you wanted to create an object model that could contain many thousands of nodes, VB isnt the language to go with.  Little did I know...
AzraSoundAuthor Commented:
Thanks for staying with me on this one ameba.  I opted for keeping a Session (collection) and the case classes:

- Session
     - Case
          - InputData
          - OutputData
          - PumpingUnitData
          - MotorUnitData

The min/max/units information was moved to helper classes as suggested.  The bulk of the code has already been rewritten and performance is superb.  Thanks for your time and expertise.
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.