DMD and GDC are unnecessarily using heap allocations for closures

Siarhei Siamashka siarhei.siamashka at gmail.com
Sun Dec 10 16:50:11 UTC 2023


On Monday, 30 May 2022 at 11:49:44 UTC, max haughton wrote:
> On Monday, 30 May 2022 at 06:47:24 UTC, Siarhei Siamashka wrote:
>> Consider the following example:
>>
>> ```D
>> import std.algorithm, std.range, std.stdio;
>>
>> [...]
>
> Also it's worth noting you can actually make some of these 
> range patterns nogc via using zip(repeat(closureVar), blah).map

Yes, this works and

```D
long binary_search(long n, long val, long i) {
   return iota(1, i + 1).map!(x => n / x == 
val).assumeSorted.upperBound(false).length;
}
```

can be indeed rewritten as

```D
long binary_search(long n, long val, long i) @nogc {
   struct C { long n, val; }
   return zip(repeat(C(n, val)), iota(1, i + 1)).map!(x => x[0].n 
/ x[1] == x[0].val)
                                                
.assumeSorted.upperBound(false).length;
}
```

Results in roughly no performance changes for LDC thanks to 
GC2Stack already taking care of it before, a major performance 
improvement for GDC (6.467s -> 4.553s) and a major performance 
regression for DMD (10.024s -> 13.733s). I'm not concerned about 
DMD, because it is known to be bad at optimizing this stuff 
anyway. But the readability of the code suffers, even though this 
is without any doubt a usable escape hatch for `@nogc` programs. 
Thanks a lot for the hint.

Are there no other better solutions/workarounds planned in the 
compiler frontend? As a random experiment I tried to plaster the 
`scope` keyword everywhere and this unsurprisingly had no effect.


More information about the Digitalmars-d mailing list