"temporary" templates

Steven Schveighoffer schveiguy at gmail.com
Tue Nov 26 17:03:04 UTC 2019


Every time you instantiate a template, it consumes memory in the 
compiler for the duration of compilation.

For example, consider the silly template:

```
template SumAll(size_t x)
{
    static if(x == 0) enum SumAll = 0;
    enum SumAll = x + SumAll!(x - 1);
}
```

Instantiate this for SumAll!100_000, and you generate... oh wait. It 
doesn't work (too much recursive templates). But THIS even sillier 
template does:

```
template SumAll(size_t x)
{
    static if(x == 0) enum SumAll = 0;
    enum SumAll = SumAllHelper!(1, x);
}

template SumAllHelper(size_t min, size_t max)
{
     static if(min == max)
     {
         enum SumAllHelper = min;
     }
     else
     {
         enum mid = (min + max) / 2;
         enum SumAllHelper = SumAllHelper!(min, mid) + SumAllHelper!(mid 
+ 1, max);
     }
}
```

Compiling a program with SumAll!100000 consumes 1.4GB of RAM.

You'll notice that I'm pretty much NEVER going to instantiate 
SumAllHelper directly, and never access ANY of the items inside the 
template. Yet the compiler stores every single instantiation of 
SumAllHelper "just in case" we need it again later. The cost for this 
convenience is astronomical.

Even if I do SumAll!100001, it still adds another 300MB of templates, 
saving *some* memory, but not enough to justify the caching.

If you look in std.meta, it's full of these style linear recursion or 
divide-and-conquer recursion algorithms, often with a "Impl" pair to the 
main template. Each one of these generates potentially hundreds or 
thousands of template instances that are used ONCE.

But what if we could MARK SumAllHelper such that it's result should be 
used once? That is, such that it's only generated temporarily, and then 
thrown to the GC? This means that the memory consumption only happens 
for SumAll, and not SumAllHelper (well, not permanently anyway). This 
means that each call to SumAll is going to re-generate some templates of 
SumAllHelper. But who cares? I'd rather have the thing compile with the 
RAM I have then be uber-concerned with caching everything under the sun.

With the compiler now supporting garbage collection, this can be a 
possibility. Is it feasible? Is it difficult? Do we actually need to 
mark SumAllHelper with some @temporary attribute or can the compiler 
figure it out?

Note, all these thoughts are happening because I am actually working on 
reducing the memory footprint of a project that makes heavy use of 
std.meta.NoDuplicates, which uses similar techniques as I've outlined 
above and generates a LOT of templates. By switching to a different 
design that avoids the need for it, I've reduced the compile-time 
footprint from over 10GB to 3.5GB. But I wish it would "just work"

-Steve


More information about the Digitalmars-d mailing list