I needed to make a configurable container with the strong requirement to use as little RAM as possible while maintaining a good level of Item indexing.
I was happy for a long time with the implementation when the Items were simple.
But starting to use bigger structures as Items, I ruined the CPU performance because to set a simple field of structure, I need to read the whole structure temporaly for change one field.
I tried to do some dirty tricks to have a reference of the whole items structures to just change one field, but the compiler doesnât like to give a reference. It says: âIllegal attribute for discriminant- dependent componentâ. Itâs failing in the function âReferenceâ
Does anybody know a workaround for this? May I start from scratch with another configurable container without a discriminant?
Here is a simplified example:
generic
type ItemType is private;
type Kind_of_Item is (<>);
package Containers is
type NodeType (Valid : Boolean := False) is record
case Valid is
when False => null;
when True => Content : ItemType;
end case;
end record;
type DT_Actives is array (Kind_of_Item) of Boolean;
type XC_Content is array (Kind_of_Item) of NodeType;
type XC_Contentptr is access all XC_Content;
type Container is tagged record
cont : aliased XC_Content;
end record;
function Get (S: Container; K: Kind_of_Item) return ItemType is (S.cont(K).Content);
procedure Set (S: in out Container; K: Kind_of_Item; Item : ItemType);
--- Dirty tricks to try to ennhace speed:
type Reference_Type (Element : not null access ItemType) is null record
with Implicit_Dereference => Element;
function Reference
(S : aliased in out Container; I : Kind_of_Item) return Reference_Type;
function Reference
(S : aliased in out Container;
I : Kind_of_Item) return Reference_Type is
(Reference_Type'(Element => S.cont(I).Content'Access));
end Containers;
with Containers;
procedure Main is
type MyStructure is record
A : Integer := 1;
B : Integer := 1;
end record;
type MyKindOfItem is (kind1, kind2, kind3);
package mypackage is new Containers(MyStructure,MyKindOfItem);
--Now we are happy to active only the needed kind to save RAM:
Actives : mypackage.DT_Actives :=
(Kind1 => True,
Kind2 => False,
Kind3 => True);
mycontainer : mypackage.Container;
temp : MyStructure;
begin
-- I just want to change B field of kind3. Need to temporary read all
temp := mycontainer.Get (kind3);
temp.B := 5;
mycontainer.Set (kind3,temp);
end Main;
My first note is about the design. Container is an implementation. It has no meaning by itself. So Get/Set have no meaning too. Your type must rather have operations reflecting the problem space. I would declare an opaque type with an array as a private implementation.
Typically for implementation I use an unbounded array of access types. The corresponding generic package is this:
The package maintains deallocation. If you store an item created by new the container manages the reference once put into the container. Get returns a pointer, so there is no copying involved. Discriminants is no problem.
A more elaborated schema, that supports limited types as well, involves smart pointers and containers of smart pointers:
Operations are defined on the specialized pointers (handles).
Thank you, Dmitry. I will deeply study your links.
Let me ask you, just to double check if it can work for me in the meanwhile I study them. I have these previous requirements:
No dynamic memory is allowed. Every object needs to be known at compilation time.
I cannot use the ânewâ operator, or at least I only can use it in a time-constrained boot sequence. (Better in a storage pool arena like)
The Index_Type will need to be the same and shared across a lot of software packages. But for another different âconfigurationsâ, some Index will not be used and any presumible space for them never used to save RAM.
For example:
â Every software will share and use this:
type Index is (Cars, Motorbikes, Boats);
But for example, there is a software for making wheels that will never use âBoatsâ elements, (although we need to use the same Index_type) and no RAM space will be reserved for them.
In any case, thank you for give me a link to your container solution!. I will study anyway
thank you!, It compiles with Unrestricted_Access!.. but now Iâm a little afraid to go ahead with this.
I donât have yet the knowledge to know if itâs an unsafe solution.
generic -- Containers
type Itemtype is private;
type Kind_of_Item is (<>);
package Containers is
type Nodetype (Valid : Boolean := False) is record
case Valid is
when False => null;
when True => Content : Itemtype;
end case;
end record;
type Container is array (Kind_of_Item) of Nodetype;
end Containers;
with Containers;
procedure Example is
type Mystructure is record
A : Integer := 1;
B : Integer := 1;
end record;
type Kind_ID is (Kind1, Kind2, Kind3);
package Structure_Containers is new
Containers (Itemtype => Mystructure, Kind_Of_Item => Kind_ID);
Container : Structure_Containers.Container :=
(Kind1 | Kind3 => (Valid => True, others => <>), Kind2 => <>);
begin -- Example
Container (Kind3).B := 5;
end Example;
except for added complexity? DT_Actives has no effect, and your array will typically use the same amount of storage regardless of the values of its components.
Yes, sorry, in my simplified example, I forgot to write a constructor function that receives the boolean array:
mycontainer : Container := F_Construye(Actives);
where the constructor was:
function F_Construye (Actives : DT_Actives) return Container is
begin
return result : Container do
for I in DT_Actives'Range loop
if Actives (I) then
result.cont (I) := NodeType'(Valid => True, Content => F_Init);
else
result.cont (I) := NodeType'(Valid => False);
end if;
end loop;
end return;
The âF_Initâ function is passed to the generics to Initialise every ItemType
Your example can indeed build the container directly.
Ok, so I had to do this for a project when there were still some bugs in the GNAT implementation of Ada 2012⌠As I recall, my solution was something like this:
Task Type Whatever(Index : Positive) is --âŚ
Type Handle is not null access Whatever;
Type Tasks is array(Positive range <>) of Handle;
Function Make_Tasks( Number : Natural ) return Tasks is
Count : Positive := 1;
Function Make return Handle is
Begin
Return Result : Constant Handle := new Whatever'(Index => Count) do
Count:= Positeve'Succ(Count);
End return;
End Make;
Begin
Return Result : Tasks:= (1..Number => Make);
End Make_Tasks;
But if you replace others => <> with Content => Init in my example, it is still equivalent to yours, simpler, and probably faster. When Kind_ID is large you have to write a large aggregate either way, so having the container constructor doesnât buy you much, if anything.
Your original post emphasizes a need to minimize storage, be âfastâ, and avoid heap usage. I canât think of any way to do all those together. Directly accessing the array will be fast, but will either result in unused storage (most compilers, including GNAT) or involve the heap (Janus/Ada implements variant records this way). Minimizing storage and avoiding heap would probably involve a custom bounded map, but that would be slower.