VO 32 Bit limitations

Public support forum for peer to peer support with related to the Visual Objects and Vulcan.NET products
User avatar
ArneOrtlinghaus
Posts: 384
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

VO 32 Bit limitations

Post by ArneOrtlinghaus »

With our always growing VO program for certain power users we quickly reach the 2 GB of the data segment for 32Bit programs, so that the program stops with "not enough memory" or similar messages.
Of course we with the transition to Dotnet some of this behavior will be better, but we are still about at least 3/4 year away from going to X# programs for our customers.

One part is the the VO dynamic memory that we use. We need between 96 MB and 256 MB of dynamic memory that is doubled in the execution.

But the main reason for the large usage of data is the memory consumation only because of loading dlls.
I have made an analysis with the sysinternals program VMMAP for one dll of media size containing 349 classes, mainly windows. Loading this dll without creating an object uses already 23MB of data. Having loaded about 40 dlls this summarizes already to about 700 MB in the data segment, mainly "Image" and "Heap memory".

I tried to modify the linker settings in the project settings, but this has not much influence.
Removing references to Dlls helps a little bit, especially the GUI classes and dlls that include the GUI classes need much memory. Removing seldom classes to other Dlls helps, but this is much work, because it needs hundreds of classes moved to see a difference.

Does anybody has an idea for optimizations? (Please do not answer that removing code will help....)

Thank you
Arne
Attachments
VMMapOutput.png
VMMapOutput.png (48.58 KiB) Viewed 338 times
User avatar
wriedmann
Posts: 3644
Joined: Mon Nov 02, 2015 5:07 pm
Location: Italy

VO 32 Bit limitations

Post by wriedmann »

Hi Arne,

I do not have a solution for you, unfortunately - only a confirmation.

When you load a VO DLL, all classes that are contained are loaded in memory. I had asked Uwe Holz many, many years ago if that could be eventually changed, and his answer was that this would destroy the base on which the entire VO runtime system was built.

Therefore I see the only possibility for you to reduce the number of classes in one application, maybe to split the application in parts, or maybe load the accounting part only when needed, hoping that the same person does not needs accounting and stock managment at the same time and in the same running copy.

For sure, this is not an easy job, and maybe it could make a transition to X# a bit more urgent.

Wolfgang
Wolfgang Riedmann
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
Jamal
Posts: 314
Joined: Mon Jul 03, 2017 7:02 pm

VO 32 Bit limitations

Post by Jamal »

Hi Arne,

Robert posted the following on Feb 2, 2017 to xsharp.public.vovulcan newgroups (not sure if it is still running).

There are 2 memory sizes that you need to manage:

1) SetMaxDynSize()
This controls the maximum amount of dynamic memory your app can use. The default is 16 Mb. On modern machines I would recommend to increase that. For most of My apps I set it to max 256 Mb. This memory is measured in bytes, so I use SetMaxDynsize
(nMegabytes * 1024 * 1024)

2) VO does not directly physically allocate all the memory from 1) but only "reserves" it from the OS. It allocates memory when it needs it. The normal behavior is that it allocates memory in 64 Kb pages. When you call DynSize() at the beginning of
your app you tell the GC to preallocate the # of pages that you specify. I usually tell it to allocate 256 pages (of 64 Kb, so 16 Mb). The effect of that is that the Garbage collector will normally run for the first time when all the 16 Mb has been
used. It will then try to free unused memory. If the free memory after the collector run is insufficient then it will allocate one more page, and continues to do so until all the reserved pages have been used.
If your app allocates a lot of memory (for example large arrays with data) you are usually better of to preallocate some pages, because otherwise each time the limit of the allocated memory is reached then the GC will run and afterwards detect that
it did not free enough memory. If you see a slow down in your apps then most likely something like this is happening.

In one of my very memory hungry apps I have added some code to a timer on the shell. In this code I am monitoring the amount of memory that is allocated. When this memorty reaches a certain "high water" mark I force the collector to run. When after
this collector run the free memory is still not very much, then I call DynSize() to allocate a few pages at the same time. This helps to delay the collector from running too often. I am even using this to resize the Reserved memory when needed.

Apart from the memory sizes you should also look at MaxRegisteredAxits(). When you increase the memory then the effect will be that the GC will run less frequently, This also means that more objects will remain in memory with a pending Axit call. You
could (and will) get a situation where you have enough memory but the Axit table is full. Something similar is true for Registered Kids and the KidStackSize().

Below is the code that I am running from my timer which takes care of this all.
Feel free to use it (at your own risk ;) )

Btw there is a topic in the VO Help file (Runtime Memory Management in VO) in which I have described some of these internals in VO.
And yes, if you are wondering why you have to take care of all of this: this should be have been handled automatically by the runtime.
I have suggested to change this many moons ago, but Brian did not think this was important and was afraid that changes in this area would break other things.
And fortunately in .NET all of this is handled by the .NET runtime in a much better way.

Btw: I would not set DynSize() to a lower number, unless you really have to. This would only be necessary when other apps are suffering. All memory is virtual, so if you are not using this memory then the OS will move unused pages to the swapfile, which does not hurt.

Code: Select all

METHOD CheckDynMemSize() CLASS MyShell
     LOCAL nActualSizeAllocated AS DWORD
     LOCAL nActualSizeused     AS DWORD
     LOCAL nActualSizeMax     AS DWORD
     LOCAL nMaxDynAllowed     AS DWORD
     LOCAL nMaxPages             AS DWORD
     LOCAL nNewSizeInPages    AS DWORD
     LOCAL nNewSizeInBytes    AS DWORD
     LOCAL nUsed, nMax, nCheck  AS DWORD
      //STATIC aIamUsingMemory:= {}   as Array
     STATIC lBusy AS LOGIC
     IF ! lBusy
         lBusy := TRUE
         // Check RegisterAxit
         nUsed := Memory(MEMORY_REGISTERAXIT)
         nMax  := SetMaxRegisteredAxitMethods(0)
         nCheck := DWORD(MulDiv(LONG(nMax), 90, 100))
         //DebOut("RegisterAxit", nUsed, nCheck, nMax)
         IF nUsed > nCheck // > 90%
             DebOut("Approaching RegisterAxit limit, COLLECTFORCED")
             Sleep(2000)
             CollectForced()
             nUsed := Memory(MEMORY_REGISTERAXIT)
             IF nUsed > nCheck
                 SetMaxRegisteredAxitMethods(DWORD(nMax * 1.5)) // 50% more
             ENDIF
         ENDIF
         // Check RegisterKid
         nUsed := Memory(MEMORY_REGISTERKID)
         nMax  := SetMaxRegisteredKids(0)
         nCheck := DWORD(MulDiv(LONG(nMax), 90, 100))
         //DebOut("RegisterKid", nUsed, nCheck, nMax)
         IF nUsed > nCheck // > 90%
             DebOut("Approaching RegisterKid limit, COLLECTFORCED")
             Sleep(2000)
             CollectForced()
             nUsed := Memory(MEMORY_REGISTERKID)
             IF nUsed > nCheck
                 SetMaxRegisteredKids(DWORD(nMax * 1.5)) // 50% more
             ENDIF
         ENDIF
         // Check KidStackSize
         nUsed := Memory(MEMORY_STACKKID)
         nMax  := SetKidStackSize(0)
         nCheck := DWORD(MulDiv(LONG(nMax), 90, 100))
         //DebOut("KidStackSize", nUsed, nCheck, nMax)
         IF nUsed > nCheck // > 90%
             DebOut("Approaching KidStack limit, COLLECTFORCED")
             Sleep(2000)
             CollectForced()
             nUsed := Memory(MEMORY_STACKKID)
             IF nUsed > nCheck
                 SetKidStackSize(DWORD(nMax * 1.5)) // 50% more
             ENDIF
         ENDIF

         nMaxDynAllowed         := 1024*1024*256
         nMaxPages              := nMaxDynAllowed /0x10000
         nActualSizeAllocated := DynInfoSize() * 0x10000     // DyInfoSize returns the number of 64Kb pages
         nActualSizeused         := DynInfoUsed()
         nActualSizeMax         := SetMaxDynSize(0)
         // When we have used 80 % or more of the allocated memory, then it is time to allocate some more pages
         IF nActualSizeused > nActualSizeAllocated * 0.80
             CollectForced()
             nActualSizeused         := DynInfoUsed()
//             Debout("MaxAllowed ",nMaxDynAllowed, nMaxPages)
//             Debout("MaxDynSize ",nActualSizeMax, nActualSizeMax/0x10000)
//             Debout("DynSizeUsed",nActualSizeUsed, nActualSizeUsed/0x10000)
//             Debout("Allocated  ",nActualSizeAllocated, DynInfoSize())

             IF nActualSizeused > nActualSizeAllocated * 0.80
                 nNewSizeInPages := DynInfoSize() +32                  // 32 pages = 2 Mb pages extra
                 nNewSizeInBytes := nNewSizeInPages * 0x10000
                 //Debout("Allocating new dynamic memory, old size (in pages) ",DynInfoSize(), " new size ",nNewSizeInPages)
                 // Make sure that we do not allocate more then the DynInfoMax. If we reach that, then we need to set the max to a higher number
                 IF nNewSizeInBytes >= nActualSizeMax
                     IF nNewSizeInPages >= nMaxPages
//                         MessageBox(NULL_PTR, Cast2Psz("The program is using a lot of memory at this moment. This will have a negative effect on the performance. " + CRLF+;
//                                           "Please close some open windows when possible." +CRLF+ ;
//                                           "When you see this message repeatedly then please restart the program"), Cast2Psz("Program running low on memory"), 0)
                         //Debout("Resize Max Dyn to ", nMaxDynAllowed, nMaxDynAllowed/0x10000)
                         SetMaxDynSize(nMaxDynAllowed)   // Allocate some extra memory just to be sure
                     ELSE
                         //Debout("Resize Max Dyn to ", nNewSizeInBytes, nNewSizeInBytes/0x10000)
                         SetMaxDynSize(nNewSizeInBytes)   // Allocate some extra memory just to be sure
                         DynSize(nNewSizeInPages-1)
                     ENDIF
ELSE
                     //Debout("Allocate", nNewSizeInPages)
                     DynSize(nNewSizeInPages)
                 ENDIF

             ENDIF
         ENDIF
         lBusy := FALSE
     ENDIF
RETURN NIL

Jamal
User avatar
ArneOrtlinghaus
Posts: 384
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

VO 32 Bit limitations

Post by ArneOrtlinghaus »

Hi Jamal,
thank you for sending me the code. I have implemented these functions with the only difference that the parameters are set once at the beginning of the program. The dynamic memory does not give many problems so. Most of the cases the errors happen when the program tries to load another DLL dynamically.

I will add a collectforce before loading the Dlls. Probably it may help a little bit.

> many moons ago

Yes, right, I have stopped counting the moons but it must be many, many :blink:

Arne
User avatar
robert
Posts: 4225
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

VO 32 Bit limitations

Post by robert »

Arne,

CollectForced() before loading a DLL will not make a difference. It moves things around in already allocated memory but will not free any memory.
The problem with loading DLLs later 'on demand' is that the OS most likely cannot find a hole in memory with enough space to fit the DLL. Memory will then be used by:
- The EXE and Other DLLs loaded into memory
- Static Memory allocated by your app and the runtime (for example the runtime allocates a block of memory to store the list of classes and methods)
- Dynamic memory allocated by your app. To be more precise: Dynamic memory reserved by your app. Your app by default reserves 2 dynamic memory pages of the size of MaxDynSpace(). The default for this is 16Mb for each page. This memory gets allocated 'when needed'. However the address space is reserved immediately. DynInfoSize() tells you how much of this memory has actually already been allocated. You can force the runtime to physically allocate the memory with DynSize().

If you want to improve things you could consider:
- Splitting the dynamically loaded DLLs into smaller ones. Then it might be easier for the OS to find a hole in memory where it fits
- Loading DLLs earlier , so allocated dynamic and static memory will not have taken the 'large spots'.


Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
User avatar
ArneOrtlinghaus
Posts: 384
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

VO 32 Bit limitations

Post by ArneOrtlinghaus »

Robert,
thank you for answering.

I did not think on the the possible fragmentation of the memory. This is another possibility for our errors apart from the 2 GB.

> - Splitting the dynamically loaded DLLs into smaller ones. Then it might be easier for the OS to find a hole in memory where it fits

We have already split the program into many dlls and it seems that it has sometimes the disadvantage of adding overhead for loading every dll. Loading 80 of all 108 existing Dlls plus the Crystal Reports runtime engine for example normally is possible. But it means that the Virtual size has reached already over 1,5 GB without having opened many windows. Normally about 40 dlls are loaded.

The program items themselves also need memory. To avoid problems with the garbage collector we store much data using Memalloc, ... for example when reordering tables in memory. Of course this can be a source of fragementation. Some time ago I found a memory leak and I am still searching for possible places, but could not find any until now. Additionally ODBC seems to take memory for every open cursor and in a variable amount.

Arne
User avatar
ArneOrtlinghaus
Posts: 384
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

VO 32 Bit limitations

Post by ArneOrtlinghaus »

I have made some memory analysis looking into a productive program of a a colleague on a terminal server with the program VMMAP without letting him know :cheer:

- VMMapfrag.png
- Main parts of the memory usage are:
- total 1894MB)
- Heap 820MB
- Private Data (yellow= VO Dynamic Memory) 560 MB
- Image (DLL code) 382 MB
- Free space left: 216 MB
The fragmentation does not look so bad: The VO dynamic memory is allocated in once. The heap is allocated by blocks of 16M, 8M, 4 M, 2 M, ... The Dll image memory is in between. There is few unused space between the different places,
-VMMapimage.png
This shows the space used by the Dlls ordered by Size desc. (What is interesting that the DLLs occupy space in the data segment, originally I learned that there are 2 GB address space and 2 GB data space for a 32 Bit prog).
Most of the Dlls occupy space beween 2 and 10 MB, few take more space.
-VMMapHeap1.png
This shows the heap distribution ordered by Size desc. I have not found out yet, where many of these blocks come from. There are many blocks that contain strings like the block examined with these texts JPDB, ATAD, ...
-VMMapLater.png
This is now 15 Minutes later
- Main parts of the memory usage are:
- total 1970 MB
- Heap 877 MB
- Private Data 562 MB
- Image (DLL code) 382 MB
- Free: 140 MB
So no dlls have been loaded, the heap has grown and the shareable memory has grown a little bit


Arne
Attachments
vmmaplater.png
vmmaplater.png (60.08 KiB) Viewed 338 times
vmmapimage.png
vmmapimage.png (74.43 KiB) Viewed 338 times
vmmapheap1.png
vmmapheap1.png (71.92 KiB) Viewed 338 times
vmmapfrag.png
vmmapfrag.png (74.01 KiB) Viewed 338 times
User avatar
wriedmann
Posts: 3644
Joined: Mon Nov 02, 2015 5:07 pm
Location: Italy

VO 32 Bit limitations

Post by wriedmann »

Hi Arne,

AFAIK there should be a possbility to increase the memory of a 32 bit application until 4 GB.

I don't know if this one solves the problem:

https://www.maketecheasier.com/increase ... 64-bit-os/

https://ntcore.com/?page_id=371

Wolfgang
Wolfgang Riedmann
Meran, South Tyrol, Italy
wolfgang@riedmann.it
https://www.riedmann.it - https://docs.xsharp.it
User avatar
ArneOrtlinghaus
Posts: 384
Joined: Tue Nov 10, 2015 7:48 am
Location: Italy

VO 32 Bit limitations

Post by ArneOrtlinghaus »

Great Wolfgang!

Everything still seems to work including the Crystal Reports.
I have now in my test program a total of 2642 MB and it still shows free memory 1612 MB!
82 Dlls loaded, 520 SQL Statements opened, 480 SQL-Server objects, 25 Windows with many tab windows each one, nearly incredible!

Now I get come to the limits of the user/gdi objects of the process and the program gets slow because of the garbage collector, but this is not a problem currently. (By the way: In my tests also the Dotnet programs get much slower with much possible garbage to collect, but they perform still much better than the VO programs)

There some characters changed in the executable by the patch program. It is a nice solution. For 32-Bit programs until now I had only found descriptions about using 3 GB instead of 2 GB, but always with advices better not to do it.

I will make further tests the next weeks and let you know.

Many thanks
Arne
Jamal
Posts: 314
Joined: Mon Jul 03, 2017 7:02 pm

VO 32 Bit limitations

Post by Jamal »

Hi Wolfgang,

That's an interesting find! I ran it on an EXE and now see available memory more than doubled.

While we are at it, i did some search on the web and found out that we can use EDITBIN.EXE from the "Visual Studio developer command prompt". It is part VC++ runtime installation.

EDITBIN /LARGEADDRESSAWARE Your32bitEXE

Refer to: https://stackoverflow.com/questions/134 ... ress-aware

https://docs.microsoft.com/en-us/cpp/bu ... in-options

BTW: editbin.exe is also part of MASM32 at http://www.masm32.com. After installation it will be in BIN folder. It is old, but it does the same thing.

Thanks!
Jamal
Post Reply