Generics and compilation time

I have a really simple generic package like that (spec only here):

pragma Ada_2022;

with Ada.Containers.Indefinite_Vectors;
with Ada.Strings.Text_Buffers;

generic
   type Element (<>) is tagged private;
   with procedure Element_Image (Output : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class;
                                 Item   : Element) is <>;
package Gir_Reader.Generic_Lists is

   use type Element;

   package Element_Lists is new Ada.Containers.Indefinite_Vectors
     (Index_Type   => Positive,
      Element_Type => Element);

   type Element_List is new Element_Lists.Vector with null record
     with Put_Image => Image;

   procedure Image
     (Output : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class;
      Item   : Element_List);

end Gir_Reader.Generic_Lists;

Without using it, compilation of my whole program takes around 11s. Each time I instantiate this package, I get 3s more compilation time. Now, I’m around 25s with 5 instantiations.
If instead of using a generic I just repeat the code, the compilation time penalty is negligible.

Are there reasons for such compilation penalty using generics?

Generics are known to not be that efficient with GNAT. However, the time penalty that you are getting is way too large to be acceptable.

Could you provide information about what compiler version you are using and which flags are enabled?

Best regards,
Fer

Thanks Irvise.

Without my own generic package, the time penalty is not so negligible as I thought. I had just tried with 2-3 copy-pastes. With the whole use cases in my code, I get around the same compile time penalty.

So this compilation time is from new Ada.Containers.Indefinite_Vectors.

My computer is quite old (AMD Phenom II X3 from 2009). That may explain part of the problem. But well, 3s for that :->

I’m on archlinux using alire with standard flags.

   Ada_Compiler_Switches := External_As_List ("ADAFLAGS", " ");
   Ada_Compiler_Switches := Ada_Compiler_Switches &
          (
            "-Og" -- Optimize for debug
           ,"-ffunction-sections" -- Separate ELF section for each function
           ,"-fdata-sections" -- Separate ELF section for each variable
           ,"-g" -- Generate debug info
           ,"-gnatwa" -- Enable all warnings
           ,"-gnatw.X" -- Disable warnings for No_Exception_Propagation
           ,"-gnatVa" -- All validity checks
           ,"-gnaty3" -- Specify indentation level of 3
           ,"-gnatya" -- Check attribute casing
           ,"-gnatyA" -- Use of array index numbers in array attributes
           ,"-gnatyB" -- Check Boolean operators
           ,"-gnatyb" -- Blanks not allowed at statement end
           ,"-gnatyc" -- Check comments
           ,"-gnaty-d" -- Disable check no DOS line terminators present
           ,"-gnatye" -- Check end/exit labels
           ,"-gnatyf" -- No form feeds or vertical tabs
           ,"-gnatyh" -- No horizontal tabs
           ,"-gnatyi" -- Check if-then layout
           ,"-gnatyI" -- check mode IN keywords
           ,"-gnatyk" -- Check keyword casing
           ,"-gnatyl" -- Check layout
           ,"-gnatym" -- Check maximum line length
           ,"-gnatyn" -- Check casing of entities in Standard
           ,"-gnatyO" -- Check that overriding subprograms are explicitly marked as such
           ,"-gnatyp" -- Check pragma casing
           ,"-gnatyr" -- Check identifier references casing
           ,"-gnatyS" -- Check no statements after THEN/ELSE
           ,"-gnatyt" -- Check token spacing
           ,"-gnatyu" -- Check unnecessary blank lines
           ,"-gnatyx" -- Check extra parentheses
           ,"-gnatW8" -- UTF-8 encoding for wide characters
          );

and

toolchain.use.gnat=gnat_native=14.2.1
toolchain.use.gprbuild=gprbuild=22.0.1
toolchain.external.gnat=false

Have a nice day!

Out of curiosity, if you remove the Put_Image function and the aspect for it on you main type does that change the timing at all? Asking because Put_Image is a relatively new feature, so maybe the implementation for it isn’t quite optimized.

Also are you using HDD or SSD type drives? The binding stage is going to generate files and if your antivirus scans them (a lot do by default unless disabled), that might be getting slow on an HDD type drive. Not saying its an acceptable time loss, but trying to isolate the cause.

Thanks for the advice.

Without Put_Image aspect, I get almost the same compilation time (37s vs 39s with 10 instantiations).

I’m using an SSD drive and no antivirus (I’m on linux).

You mention the compile time, but how does the execution time change, if at all?

If the execution time is faster, then the longer compile time might be an acceptable tradeoff.

1 Like

It doesn’t change anything at runtime. I was just surprised to have a 3s compilation time added each time I added an instantiation. And when developing, it’s nicer to have 10s compilation time instead of around 40s :->

I have found that it helps immensely to pre-instantiate expensive-to-compile generics outside of the file where you use them. This prevents them from being re-instantiated every time you compile modified code.

For example, a string_vectors.ads might only contain:

with Ada.Containers.Indefinite_Vectors;

package String_Vectors is new Ada.Containers.Indefinite_Vectors
  (Index_Type => Positive, Element_Type => String);

Then String_Vectors will be instantiated once, cached, and reused across compilations (at least until you modify the generic body).

4 Likes

So this has nothing to do with generics; it’s due to the amount of code that you’re compiling, which is roughly the same whether you use generics or code duplication, since GNAT does instantiation by macro expansion. What might save you time is using a compiler that implements shared-code generics (DEC Ada or Janus Ada).

The first Ada compiler I used was the Rolm/Data General compiler, which took about 10 minutes to compile a “Hello, World!” program, so I don’t know what you’re complaining about.

3 Likes

Mind you, for the younger people here, what Jeffrey is talking about is a compiler dating from the early 1980’s (1983 or 1984 IIRC). In fact, that compiler got Validation Certificate nr. 2, after NYU’s Ada/Ed that got the first certificate (but the latter was an interpreter written in Setl, a language based on sets and interpreted as well, i.e. two levels of interpretation). So ROLM/Data General was the first validated Ada 1983 compiler. Note that it was still in serious use at least until 2006. Been there, done that…

Historic info added to avoid Jeffrey’s posting would be used to falsely claim “Ada is slow” [sic]. 40+ years ago, compiling and running a Java or Rust program would have taken infinitely longer… :wink:

2 Likes

Blockquote
So this has nothing to do with generics; it’s due to the amount of code that you’re compiling, which is roughly the same whether you use generics or code duplication, since GNAT does instantiation by macro expansion.

It somehow as to do. It’s just not my own generic package but the indefinite_vector generic that takes time to compile!

Thanks, I’ll try that.

It has all to do with generics. GNAT implementation of is extremely slow and consumes incomprehensible amounts of memory. It became much better in recent decade. The production code that actively used generics compiled several days then and I could not compile it on a 32-bit system. It ran out of memory!

I think AdaCore added a lot of optimisation since then, which does not kick in this concrete case because of new Ada features.

Well, of course. An instance of Indefinite_Vectors is a pretty large package. But if you hand-instantiate it, it will still take about as long to compile.

From my experience the package length had little to do with the problem. I had a lot of small generics instantiated for various scalar types (signed, unsigned x size). It blew the GNAT Pro out.

I would propose to consider instead of generics in generics to look if

  • child generics
  • formal generic instance as an argument

do better.

(Even better, avoid generics if you can. and use tagged types instead!)