comp.lang.ada
 help / color / mirror / Atom feed
* Allocators and memory reclamation
@ 2008-01-28 13:49 Maciej Sobczak
  2008-01-28 14:52 ` gpriv
                   ` (4 more replies)
  0 siblings, 5 replies; 15+ messages in thread
From: Maciej Sobczak @ 2008-01-28 13:49 UTC (permalink / raw)


Hi,

Consider the following:

procedure Foo is
   type Int_Ptr is access Integer;
   P : Int_Ptr;
begin
   P := new Integer;
   P := new Integer;
   P := new Integer;
end Foo;

procedure Main is
begin
   loop
      Foo;
   end loop;
end Main;

In Foo above, three objects of type Integer are allocated and the
storage is taken from the storage pool associated with the Int_Ptr
access type.
What I understood before is that this storage pool is torn down when
the access type itself goes out of scope and it is also when the
memory is reclaimed. As a result no memory is leaked in the above code
- I can call Foo as many times as I want without any risk of running
out of memory. This is at least what I can observe with controlled
types.

The problem is that my understanding conflicts with what I've just
found in AARM (13.11):
"By default, the implementation might choose to have a single global
storage pool, which is used (by default) by all access types, which
might mean that storage is reclaimed automatically only upon partition
completion."

This means that the implementation might turn the above well-behaving
procedure into a memory leak. Is this correct?
Can I influence this behaviour to portably ensure that memory is
reclaimed when the access type goes out of scope?

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?

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 13:49 Maciej Sobczak
@ 2008-01-28 14:52 ` gpriv
  2008-01-28 14:53 ` Lucretia
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 15+ messages in thread
From: gpriv @ 2008-01-28 14:52 UTC (permalink / raw)


On Jan 28, 8:49 am, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
> Hi,
>
> Consider the following:
>
> procedure Foo is
>    type Int_Ptr is access Integer;
>    P : Int_Ptr;
> begin
>    P := new Integer;
>    P := new Integer;
>    P := new Integer;
> end Foo;
>
> procedure Main is
> begin
>    loop
>       Foo;
>    end loop;
> end Main;
>
> In Foo above, three objects of type Integer are allocated and the
> storage is taken from the storage pool associated with the Int_Ptr
> access type.
> What I understood before is that this storage pool is torn down when
> the access type itself goes out of scope and it is also when the
> memory is reclaimed. As a result no memory is leaked in the above code
> - I can call Foo as many times as I want without any risk of running
> out of memory. This is at least what I can observe with controlled
> types.

Your understanding is incorrect. By default Ada provides deterministic
deallocation only similar to C++, and your program eventually will
crash running out of memory.  You may define your own behavior by
deriving from Root_Storage_Pool and assign your own pool
implementation for particular objects.


>
> The problem is that my understanding conflicts with what I've just
> found in AARM (13.11):
> "By default, the implementation might choose to have a single global
> storage pool, which is used (by default) by all access types, which
> might mean that storage is reclaimed automatically only upon partition
> completion."
>
> This means that the implementation might turn the above well-behaving
> procedure into a memory leak. Is this correct?

I would say it is a stretch to say that program above is well-
behaving.  What would be bad-behaving then?


> Can I influence this behaviour to portably ensure that memory is
> reclaimed when the access type goes out of scope?
>
> 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?

that will be irrelevant since default deallocation is deterministic.
You may implement any behavior if use your own pool.

>
> --
> Maciej Sobczak *www.msobczak.com*www.inspirel.com




^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 13:49 Maciej Sobczak
  2008-01-28 14:52 ` gpriv
@ 2008-01-28 14:53 ` Lucretia
  2008-01-28 16:00   ` gpriv
  2008-01-28 15:15 ` Dmitry A. Kazakov
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 15+ messages in thread
From: Lucretia @ 2008-01-28 14:53 UTC (permalink / raw)


On Jan 28, 1:49 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:

> "By default, the implementation might choose to have a single global
> storage pool, which is used (by default) by all access types, which
> might mean that storage is reclaimed automatically only upon partition
> completion."

I didn't realise this either.

Luke.



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 13:49 Maciej Sobczak
  2008-01-28 14:52 ` gpriv
  2008-01-28 14:53 ` Lucretia
@ 2008-01-28 15:15 ` Dmitry A. Kazakov
  2008-01-28 22:27   ` Maciej Sobczak
  2008-01-28 23:07   ` Randy Brukardt
  2008-01-28 22:00 ` Aurele
  2008-01-29  0:41 ` Robert A Duff
  4 siblings, 2 replies; 15+ messages in thread
From: Dmitry A. Kazakov @ 2008-01-28 15:15 UTC (permalink / raw)


On Mon, 28 Jan 2008 05:49:35 -0800 (PST), Maciej Sobczak wrote:

> The problem is that my understanding conflicts with what I've just
> found in AARM (13.11):
> "By default, the implementation might choose to have a single global
> storage pool, which is used (by default) by all access types, which
> might mean that storage is reclaimed automatically only upon partition
> completion."
> 
> This means that the implementation might turn the above well-behaving
> procedure into a memory leak. Is this correct?

It does not leak for Deallocate is called. (It would be difficult to
formalize "leaking" otherwise than "for some Allocate there was no
Deallocate called.")

The language does not prescribe the effect of a call to Deallocate on the
program's environment. Note that it might be impossible to return once
allocated system memory back to OS. So taking it naively any program would
leak.

> Can I influence this behaviour to portably ensure that memory is
> reclaimed when the access type goes out of scope?

Write your own pool, which takes its memory from the standard pool or else
statically.

> 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?

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 14:53 ` Lucretia
@ 2008-01-28 16:00   ` gpriv
  2008-01-28 22:46     ` Alex R. Mosteo
  0 siblings, 1 reply; 15+ messages in thread
From: gpriv @ 2008-01-28 16:00 UTC (permalink / raw)


On Jan 28, 9:53 am, Lucretia <lucret...@lycos.co.uk> wrote:
> On Jan 28, 1:49 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
>
> > "By default, the implementation might choose to have a single global
> > storage pool, which is used (by default) by all access types, which
> > might mean that storage is reclaimed automatically only upon partition
> > completion."
>
> I didn't realise this either.
>
> Luke.

I think what they mean here is as one allocates memory it will be
requested on the system level. This memory might be only released when
all objects are properly deallocated from the entire pool. However it
seems to be recommendation ("might") and is not what I see with GNAT.
I see memory footprint growing and shrinking as it runs and it is
never deallocating all dynamic objects at any time.

George




^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 13:49 Maciej Sobczak
                   ` (2 preceding siblings ...)
  2008-01-28 15:15 ` Dmitry A. Kazakov
@ 2008-01-28 22:00 ` Aurele
  2008-01-29  0:41 ` Robert A Duff
  4 siblings, 0 replies; 15+ messages in thread
From: Aurele @ 2008-01-28 22:00 UTC (permalink / raw)


On Jan 28, 8:49 am, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
>
> procedure Foo is
>    type Int_Ptr is access Integer;
>    P : Int_Ptr;
> begin
>    P := new Integer;
>    P := new Integer;
>    P := new Integer;
> end Foo;
>
> procedure Main is
> begin
>    loop
>       Foo;
>    end loop;
> end Main;
>


To avoid memory leak, rewrite as...

  with Ada.Unchecked_Deallocation;

  type Int_Ptr is access Integer;
  procedure Free is new Ada.Unchecked_Deallocation( Integer,
Int_Ptr );

  procedure Foo is
     P : Int_Ptr;
       :
  begin
     P := new Integer;
       :
     Free( P );
       :
  end Foo;

  procedure Main is
  begin
    loop
      Foo;
    end loop;
  end Main;



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 15:15 ` Dmitry A. Kazakov
@ 2008-01-28 22:27   ` Maciej Sobczak
  2008-01-28 23:54     ` Adam Beneschan
  2008-01-29  9:38     ` Dmitry A. Kazakov
  2008-01-28 23:07   ` Randy Brukardt
  1 sibling, 2 replies; 15+ messages in thread
From: Maciej Sobczak @ 2008-01-28 22:27 UTC (permalink / raw)


On 28 Sty, 16:15, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
wrote:

> The language does not prescribe the effect of a call to Deallocate on the
> program's environment.

I'm not interested in the interaction on this level.
I'm interested in the pattern of calls to Deallocate in the given
program.
My understanding, initially, was that Deallocate is called kind of
implicitly when the storage pool is torn down together with its
associated access type.
It does not seem to be the case, but the storage pool itself can keep
track of the allocated blocks and at the end do "something" with them.
For example, the storage pool might be based on static memory block
that just gives new addresses for each Allocate and have empty
Deallocate - a fairly compliant implementation. When the pool goes out
of scope, so does its memory.

I understood that the memory that was used by the pool is deallocated
even in the following simple case:

type Integer_Ptr is access Integer;

but the AARM claims it can be otherwise (that the memory might be
bound to library-level pool).

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.

> > 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.


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?

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

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 16:00   ` gpriv
@ 2008-01-28 22:46     ` Alex R. Mosteo
  0 siblings, 0 replies; 15+ messages in thread
From: Alex R. Mosteo @ 2008-01-28 22:46 UTC (permalink / raw)


gpriv@axonx.com wrote:

> On Jan 28, 9:53 am, Lucretia <lucret...@lycos.co.uk> wrote:
>> On Jan 28, 1:49 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:
>>
>> > "By default, the implementation might choose to have a single global
>> > storage pool, which is used (by default) by all access types, which
>> > might mean that storage is reclaimed automatically only upon partition
>> > completion."
>>
>> I didn't realise this either.
>>
>> Luke.
> 
> I think what they mean here is as one allocates memory it will be
> requested on the system level. This memory might be only released when
> all objects are properly deallocated from the entire pool. However it
> seems to be recommendation ("might") and is not what I see with GNAT.
> I see memory footprint growing and shrinking as it runs and it is
> never deallocating all dynamic objects at any time.

GNAT 2007 (at least) ships with two utility storage pools; one which is the
global one and other for local allocations that I guess is closer to the
behavior Maciej expected:

with System.Pool_Local;
declare
   Pool : System.Unbounded_Reclaim_Pool;
   type IA is access integer;
   for IA'Storage_Pool use Pool;
begin
   --  Allocate like crazy;
end;  --  Storage is reclaimed here.



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 15:15 ` Dmitry A. Kazakov
  2008-01-28 22:27   ` Maciej Sobczak
@ 2008-01-28 23:07   ` Randy Brukardt
  1 sibling, 0 replies; 15+ messages in thread
From: Randy Brukardt @ 2008-01-28 23:07 UTC (permalink / raw)



"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
news:lneim0ua86uj$.5q4owyndn3kj$.dlg@40tude.net...
> On Mon, 28 Jan 2008 05:49:35 -0800 (PST), Maciej Sobczak wrote:
...
> > 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?

I have no idea what Dmitry is talking about. The language does require
finalization of objects when the owning access type goes out of scope.
(Another good reason to avoid conversions between access types!)

In particular, read 7.6.1(11/2) [which defines "finalization of the
collection".] Note that there is no requirement of an order on the
particular allocators, only that they all get finalized at the point of the
type declaration (in reverse, of course).

                           Randy.





^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 22:27   ` Maciej Sobczak
@ 2008-01-28 23:54     ` Adam Beneschan
  2008-01-29  9:38     ` Dmitry A. Kazakov
  1 sibling, 0 replies; 15+ messages in thread
From: Adam Beneschan @ 2008-01-28 23:54 UTC (permalink / raw)


On Jan 28, 2:27 pm, Maciej Sobczak <see.my.homep...@gmail.com> 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



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 13:49 Maciej Sobczak
                   ` (3 preceding siblings ...)
  2008-01-28 22:00 ` Aurele
@ 2008-01-29  0:41 ` Robert A Duff
  2008-01-29 11:12   ` Maciej Sobczak
  4 siblings, 1 reply; 15+ messages in thread
From: Robert A Duff @ 2008-01-29  0:41 UTC (permalink / raw)


Maciej Sobczak <see.my.homepage@gmail.com> writes:

> Hi,
>
> Consider the following:
>
> procedure Foo is
>    type Int_Ptr is access Integer;
>    P : Int_Ptr;
> begin
>    P := new Integer;
>    P := new Integer;
>    P := new Integer;
> end Foo;
>
> procedure Main is
> begin
>    loop
>       Foo;
>    end loop;
> end Main;
>
> In Foo above, three objects of type Integer are allocated and the
> storage is taken from the storage pool associated with the Int_Ptr
> access type.
> What I understood before is that this storage pool is torn down when
> the access type itself goes out of scope and it is also when the
> memory is reclaimed. As a result no memory is leaked in the above code
> - I can call Foo as many times as I want without any risk of running
> out of memory.

Some implementations might behave that way, but you should not rely on
it.

>...This is at least what I can observe with controlled
> types.

I don't understand what you mean about controlled types.
There are none in the above example, and I don't know
what example you have in mind.

> The problem is that my understanding conflicts with what I've just
> found in AARM (13.11):
> "By default, the implementation might choose to have a single global
> storage pool, which is used (by default) by all access types, which
> might mean that storage is reclaimed automatically only upon partition
> completion."

The implementation is allowed to use a local pool for Int_Ptr,
and reclaim the memory, but it is not required to.  I think
most implementations use a global pool by default.  That's
certainly the case with GNAT.

To force the reclamation, you can say:

    for Int_Ptr'Storage_Size use 1000; -- or some number

or:

    for Int_Ptr'Storage_Pool use My_Pool;

where My_Pool gets reclaimed somehow (that's up to you).
My_Pool might allocate its data on the stack, so it will
be reclaimed.  Or it might allocate its data on the global
heap, and have a Finalize that reclaims it.  Or some of
each.

I think implementations should not do such reclamation by default -- a
global heap is a better default way to do things.

> This means that the implementation might turn the above well-behaving
> procedure into a memory leak. Is this correct?

Well, yeah, but that's a strange way to put it.  The above procedure
is not well behaved, unless you are relying on some non-portable
implementation-depdendent behavior.

> Can I influence this behaviour to portably ensure that memory is
> reclaimed when the access type goes out of scope?

Yes.

> 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?

Finalization of heap objects happens when you do
Unchecked_Deallocation.  In addition, when an access type goes
out of scope, all remaining objects (the ones you did not
Unchecked_Deallocate) are finalized in an arbitrary order,
but this does not free the memory.

Finalization of heap objects has nothing to do with storage pools.
It has to do with where the access type is declared,
not when its pool is finalized.

In implementation terms, each finalizable heap object is
chained onto a doubly-linked list.  U_D removes the object
from the list, finalizes it, and frees the memory.
When the access type goes out of scope, the implementation
walks down the list and finalizes everything on it.
There is one list per access type.
Many variations of this scheme are possible, of course.

- Bob



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-28 22:27   ` Maciej Sobczak
  2008-01-28 23:54     ` Adam Beneschan
@ 2008-01-29  9:38     ` Dmitry A. Kazakov
  1 sibling, 0 replies; 15+ messages in thread
From: Dmitry A. Kazakov @ 2008-01-29  9:38 UTC (permalink / raw)


On Mon, 28 Jan 2008 14:27:23 -0800 (PST), Maciej Sobczak wrote:

> I'm interested in the pattern of calls to Deallocate in the given
> program.
> My understanding, initially, was that Deallocate is called kind of
> implicitly when the storage pool is torn down together with its
> associated access type.
> It does not seem to be the case, but the storage pool itself can keep
> track of the allocated blocks and at the end do "something" with them.

In my understanding Deallocate is just not called. One can enforce this
behavior when there is GC by applying the pragma Controlled, but not the
reverse.

> 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.

There is a linked list of controlled objects allocated in the access type
scope.

I don't think this is "good". The language should be consistent in its
choices, if the design choice was to leak memory, then it finalization must
leak as well! (:-))

>>> 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?

Again, what for?  (:-)) A properly designed program should never rely on
such stuff. I mean either there should be a GC with the properties defined,
and BTW, no GC usually tells anything about not only in which order but
also when and if something gets collected; or else it should do absolutely
nothing.

> 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;

   type Integer_Ptr is access Integer;
   P : Root_Storage_Pool'Class renames Integer_Ptr'Storage_Pool;
 
-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Allocators and memory reclamation
@ 2008-01-29 11:06 Grein, Christoph (Fa. ESG)
  2008-01-29 12:50 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 15+ messages in thread
From: Grein, Christoph (Fa. ESG) @ 2008-01-29 11:06 UTC (permalink / raw)
  To: comp.lang.ada

Dmitry wrote:
> In my understanding Deallocate is just not called. One can enforce
this 
> behavior when there is GC by applying the pragma Controlled, but not
the 
> reverse. 

No, just the reverse, pragma Controlled prevents automatic garbage
collection, see RM 13.11.3(1).


Eurocopter Deutschland GmbH
Sitz der Gesellschaft/Registered Office: Donauwoerth
Registergericht/Registration Court: Amtsgericht Augsburg HRB 16508
Vorsitzender des Aufsichtsrates/Chairman of the Supervisory Board: Dr. Lutz Bertling
Geschaeftsfuehrung/Board of Management:
Dr. Wolfgang Schoder, Vorsitzender/CEO; Friedrich-Wilhelm Hormel; Ralf Barnscheidt

CONFIDENTIALITY NOTICE 

This communication and the information it contains is intended for the addressee(s) named above and for no other persons or organizations. It is confidential and may be legally privileged and protected by law. The unauthorized use, copying or disclosure of this communication or any part of it is prohibited and may be unlawful. 
If you have received this communication in error, kindly notify us by return e-mail and discard and/or delete the communication. Thank you very much. 
It is possible for e-mails to be intercepted or affected by viruses. Whilst we maintain virus checks on our e-mails, we accept no liability for viruses or other material which might be introduced with this message. 




^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-29  0:41 ` Robert A Duff
@ 2008-01-29 11:12   ` Maciej Sobczak
  0 siblings, 0 replies; 15+ messages in thread
From: Maciej Sobczak @ 2008-01-29 11:12 UTC (permalink / raw)


On 29 Sty, 01:41, Robert A Duff <bobd...@shell01.TheWorld.com> wrote:

> Finalization of heap objects happens when you do
> Unchecked_Deallocation.  In addition, when an access type goes
> out of scope, all remaining objects (the ones you did not
> Unchecked_Deallocate) are finalized in an arbitrary order,
> but this does not free the memory.
[...]
> In implementation terms, each finalizable heap object is
> chained onto a doubly-linked list.  U_D removes the object
> from the list, finalizes it, and frees the memory.
> When the access type goes out of scope, the implementation
> walks down the list and finalizes everything on it.
[...]

This, and the replies from Adam and Dmitry give me the complete
picture of how it works. Thanks for the explanation.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: Allocators and memory reclamation
  2008-01-29 11:06 Allocators and memory reclamation Grein, Christoph (Fa. ESG)
@ 2008-01-29 12:50 ` Dmitry A. Kazakov
  0 siblings, 0 replies; 15+ messages in thread
From: Dmitry A. Kazakov @ 2008-01-29 12:50 UTC (permalink / raw)


On Tue, 29 Jan 2008 12:06:19 +0100, Grein, Christoph (Fa. ESG) wrote:

> Dmitry wrote:

>> In my understanding Deallocate is just not called. One can enforce this 
>> behavior when there is GC by applying the pragma Controlled, but not the 
>> reverse. 
> 
> No, just the reverse, pragma Controlled prevents automatic garbage
> collection, see RM 13.11.3(1).

Yes, that is what I meant

type My_Type_Ptr is access ...;
pragma Controlled (My_Type_Ptr);  -- No Deallocate ever implicitly called

Without the pragma it isn't called anyway, but it could be if there were GC
present.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2008-01-29 12:50 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-01-29 11:06 Allocators and memory reclamation Grein, Christoph (Fa. ESG)
2008-01-29 12:50 ` Dmitry A. Kazakov
  -- strict thread matches above, loose matches on Subject: below --
2008-01-28 13:49 Maciej Sobczak
2008-01-28 14:52 ` gpriv
2008-01-28 14:53 ` Lucretia
2008-01-28 16:00   ` gpriv
2008-01-28 22:46     ` Alex R. Mosteo
2008-01-28 15:15 ` Dmitry A. Kazakov
2008-01-28 22:27   ` Maciej Sobczak
2008-01-28 23:54     ` Adam Beneschan
2008-01-29  9:38     ` Dmitry A. Kazakov
2008-01-28 23:07   ` Randy Brukardt
2008-01-28 22:00 ` Aurele
2008-01-29  0:41 ` Robert A Duff
2008-01-29 11:12   ` Maciej Sobczak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox