Re: Allocators and memory reclamation



On Jan 28, 2:27 pm, Maciej Sobczak <see.my.homep...@xxxxxxxxx> wrote:

I understand that I can create my own pool with any behaviour I need.
Let's try:

with Ada.Text_IO;
with Ada.Finalization;
with System.Storage_Pools;
with System.Storage_Elements;

procedure Main is

use System.Storage_Pools;

-- used to acquire reference to the standard storage pool
type Integer_Ptr is access Integer;

procedure Test is

type My_Type is new Ada.Finalization.Controlled with null
record;

procedure Finalize (T : in out My_Type) is
begin
Ada.Text_IO.Put_Line ("Finalize");
end Finalize;

use System;

type My_Pool_Type is new Root_Storage_Pool with null record;

procedure Allocate (Pool : in out My_Pool_Type;
Storage_Address : out Address;
Size_In_Storage_Elements : in
Storage_Elements.Storage_Count;
Alignment : in
Storage_Elements.Storage_Count);
procedure Deallocate (Pool : in out My_Pool_Type;
Storage_Address : in Address;
Size_In_Storage_Elements : in
Storage_Elements.Storage_Count;
Alignment : in
Storage_Elements.Storage_Count);
function Storage_Size (Pool : in My_Pool_Type) return
Storage_Elements.Storage_Count;

procedure Allocate (Pool : in out My_Pool_Type;
Storage_Address : out Address;
Size_In_Storage_Elements : in
Storage_Elements.Storage_Count;
Alignment : in
Storage_Elements.Storage_Count) is
begin
Allocate (Integer_Ptr'Storage_Pool, Storage_Address,
Size_In_Storage_Elements, Alignment);
Ada.Text_IO.Put_Line ("allocating " &
Storage_Elements.Storage_Count'Image
(Size_In_Storage_Elements));
end Allocate;

procedure Deallocate (Pool : in out My_Pool_Type;
Storage_Address : in Address;
Size_In_Storage_Elements : in
Storage_Elements.Storage_Count;
Alignment : in
Storage_Elements.Storage_Count) is
begin
Deallocate (Integer_Ptr'Storage_Pool, Storage_Address,
Size_In_Storage_Elements, Alignment);
Ada.Text_IO.Put_Line ("deallocating");
end Deallocate;

function Storage_Size (Pool : in My_Pool_Type) return
Storage_Elements.Storage_Count is
begin
return Storage_Size (Integer_Ptr'Storage_Pool);
end Storage_Size;

My_Pool : My_Pool_Type;

type My_Type_Ptr is access My_Type;
for My_Type_Ptr'Storage_Pool use My_Pool;

P : My_Type_Ptr;

begin
Ada.Text_IO.Put_Line ("starting to allocate objects");
P := new My_Type;
P := new My_Type;
Ada.Text_IO.Put_Line ("finished with objects and leaving the
scope");
end Test;

begin
Ada.Text_IO.Put_Line ("Running test");
Test;
Ada.Text_IO.Put_Line ("Test finished");
end Main;

(the code has lengthy lines and can get broken in your readers)

In the above example I tried to create my own pool that is backed up
by the standard pool acquired from some dummy access type. The new
pool just forwards all primitive operations to the standard pool and
prints some messages to see what's going on.
It looks like I got it correctly, because it compiles and even works:

Running Test
Starting To Allocate Objects
Allocating 12
Allocating 12
Finished With Objects And Leaving The Scope
Finalize
Finalize
Test Finished

I understand (now) that two objects are allocated with the help of my
own pool. Since nobody deallocated explicitly (no
Unchecked_Deallocation was used), the Deallocate procedure was not
called and the memory is effectively leaked. I could have remembered
the allocated addresses and deallocate the dangling blocks in the
finalizer of my own pool (it is controlled after all) and this way the
pool would guard the memory and prevent the leak. It didn't, so
effectively there is a leak.
More precisely, the allocated blocks come from the standard pool and
they will be deallocated when the whole program will finish.

The interesting part is that finalizers of My_Type were called and
this is even before the pool gets finalized itself (I've checked it by
overriding Finalize for My_Pool_Type). This is good, because after
that only raw memory remained to be reclaimed.
The question is - who called the finalizers of allocated objects? Any
AARM paragraphs for this would be welcome.

As Randy pointed out, 7.6.1(11) is the key; it discusses when objects
created by an <allocator> are finalized. It also says that those
objects are finalized in an arbitrary order.


Another question relates to the order of finalizing objects. If the
storage pool is torn down when the access type goes out of scope, is
the order of finalizing objects guaranteed?

AFAIK, it is not. Why should it be?

Why not? :-)

If not, then there is additional (automatic) question: how can I make
it guaranteed? Since finalizers seem to be called by some magic, then
I don't think there is any way for me to influence the behaviour here
- at least not by providing my own pool, which does not even get a
chance to have anything to say on this subject.

I'm not completely clear on what you are trying to accomplish. Are
you trying to ensure that if you allocate a number of objects of a
controlled type, then when those objects are finalized, you want them
to be finalized in a particular order, for example in the reverse
order in which you allocated them?

If this is what you want, you'll need to invent your own solution, but
I think it's doable. I can envision an implementation where all the
allocated records of a type are linked together in a list; then when
one of these is finalized according to 7.6.1(11), the Finalize routine
recognizes this and finalizes *all* of the objects in the linked list,
perhaps setting a flag in the records to indicate that the
finalization took place. Then when the other objects are finalized
according to 7.6.1(11), the Finalize routine would recognize that the
finalization has already been done, and do nothing. If you do this,
you can certainly control the order in which allocated objects are
finalized. But I think something like this has to be done. There's
nothing in the language that I know of to assert any control over the
order in which allocated objects are finalized.


There is a related problem which I've found while playing with the
above code example: I cannot have a pointer to the standard pool.
In other words, this:

type Pool_Ptr is access Root_Storage_Pool'Class;
PP : Pool_Ptr := Integer'Storage_Pool'Access;

is not possible, because the standard storage pool is not aliased.
How can I get a reference to the standard pool so that I don't have to
repeat the "Integer'Storage_Pool" million times?
What about this:

P : Root_Storage_Pool'Class := Integer_Ptr'Storage_Pool;

I expect that P will be a reference to the standard pool, not its copy
(it cannot be a copy, because Root_Storage_Pool is limited). Is this
reasoning correct?

No. Declaring an object of a limited type does *not* create a
reference; it creates an object. Here, the compiler will think you're
trying to make a copy of a limited object, and it will complain. Try
"renames".


Last but not least: does AARM 13.11/20 mean that the above program is
incorrect? How to implement it correctly, then?

You're probably OK in this particular case, since you're simply
passing through the arguments that the compiler is passing to another
Deallocate. But the potential for trouble is that when the
implementation generates code for an <allocator> or for a call to an
Unchecked_Deallocation instance, it may do other things besides call
Allocate or Deallocate, and a direct call to Allocate or Deallocate
doesn't do those things. It seems unlikely to me that, for an
implementation, those "other things" would do anything that involves
the storage pool, which means that this pass-through usage should be
fine. But I could be wrong.

-- Adam
.



Relevant Pages

  • Re: Allocators and memory reclamation
    ... I'm interested in the pattern of calls to Deallocate in the given ... It does not seem to be the case, but the storage pool itself can keep ... procedure Finalize is ... Allocate (Integer_Ptr'Storage_Pool, Storage_Address, ...
    (comp.lang.ada)
  • Re: OT: UKs new swimming pool policy
    ... are holding swimming sessions specifically aimed at Muslims, ... if the public pool facility is catering to 'unique ... Another might be a dress standard. ...
    (sci.electronics.design)
  • Re: Finding out minimal allocation unit
    ... Here's the debug storage pool from SAL ... -- Number of times Allocate has been called successfully. ... -- The first few elements of each free block contain the block ...
    (comp.lang.ada)
  • Re: Delayed interrupt work, thread pools
    ... the main mechanism we provide to do that is workqueues. ... work queue or even a dedicated per-cpu one will cause a context to ... Thus the idea of turning workqueues into some kind of pool of threads. ... the kernel can allocate a new bunch and dispatch more work. ...
    (Linux-Kernel)
  • Re: User mode APCs, paged or non-paged heap?
    ... application chunk of memory managed by the alloc, malloc, calloc, etc. used ... non-paged pool and the other the paged pool. ... You can allocate from either ... I think 'heap' is not to be used in kernel mode. ...
    (microsoft.public.development.device.drivers)