Collapse templated overloads in std.utf - #11093
Conversation
Keep public S-templated string APIs as tiny trampolines that cast to a shared const(C)[] implementation. Range overloads exclude strings so they do not duplicate the string body. Decode and decodeImpl are untouched.
Add a reusable worktree-based DMD measurement script and a std.utf driver that instantiates stride, strideBack, codeLength, isValidUTF, validate, and byUTF across string encodings and range types.
Fix measure_templates.d to read DMD N (M distinct) summary lines instead of treating file paths as template names. Include the before/after report for the string-overload collapse.
|
Why object file size, specifically? That doesn't necessarily translate to a smaller final binary... |
|
@Herringway It acts as a confirmation that we're achieving the desired results. Hence it's a secondary metric. |
I've seen many quantifiable problems attributed to template bloat, but object file size is not one of them. Compilation times and final binary sizes being two big ones that come to mind. Two big metrics that we certainly don't want to backslide on, as well. |
|
Object file size isn't the problem we're trying to solve, template bloat is, but template shows up in object file size so it's a usable metric to checking the results of our effort. |
But it's not an important metric. Why did you choose the only metric that ISN'T important? We would all like smaller binaries and shorter compile times. Please measure those instead so that we can see the real benefits of these changes! I've seen too many big changes happen over the years that cause regressions in these areas, and I would appreciate the assurance that it is not regressing further here. |
|
This PR is trading off performance for code size. The index-less version needs to do less work. Yet, for arrays you are making it do the same work as the indexed version by passing 0 to the other versions. These tradeoffs might not be desirable. And it's not clear why you don't do the same for the non-array versions if that is the desired outcome. IMO, runtime performance trumps template bloat. There is a missed opportunity here, in that the compiler already does a lot of free implicit conversions without any templates at all. Why don't we have a As a trivial matter, you shouldn't need casts here to call the non-templated versions, there is a danger here of using the blunt cast which might not work right for all types. I'm going to be blunt, this kind of work is wasting everyone's time. I'm not going to review any more of these vibeslop PRs. If this were my repository, and you were sending me these kinds of PRs you would get a ban. |
|
@Herringway This is for you. Compile timeWall-clock Test Driver
Module (
|
| old (ms) | new (ms) | delta (ms) | |
|---|---|---|---|
| min | 66 | 67 | +1 |
| median | 69 | 68 | −1 |
| mean | 68 | 67 | −1 |
| max | 69 | 68 | −1 |
- old samples (ms): 69, 68, 69, 69, 66
- new samples (ms): 68, 67, 68, 68, 67
Unittests (dmd -c -main -unittest -version=StdUnittest std/utf.d)
| old (ms) | new (ms) | delta (ms) | |
|---|---|---|---|
| min | 451 | 446 | −5 |
| median | 456 | 451 | −5 |
| mean | 457 | 453 | −4 |
| max | 465 | 463 | −2 |
- old samples (ms): 456, 451, 455, 465, 460
- new samples (ms): 446, 449, 451, 459, 463
Compile time is noise at this scale: a few milliseconds on a ~450 ms unittest compile, and the driver/module deltas sit inside sample spread. But I find it fascinating that nearly every time moved down.
|
These numbers suggest that this approach doesn't have any particularly meaningful impact. Perhaps it would be worth looking into @schveiguy's suggestions. |
this is not a good use of your ai tokens; phoboes style is bad at templates and I bet you told it to "maintain style" or gave it the style guide Theres allot of simplicity on the table; like my 5 line nullable vs the 1000 lines; you should be getting more then 4% |
The LLM's I have used, have all done the style automatically of the targeted codebase. No instruction. |
Rationale
This PR is part of a series of PR's requested by @WalterBright to explore different methods of reducing template usage and how much we can expect to reduce template usage by in Phobos 3.
The purpose of this PR is to reduce the number of instantiated templates in object files that are generated by
std.utf. Given the pervasive usage ofstd.utfthroughout the ecosystem reducing template usage here can significantly reduce the template bloat. I used a test harness app to produce these stats but I did not include the harness with this PR.Pre-review checklist
LLM/AI disclosure
This PR was written entirely by an LLM/AI.
Before / After Metrics
Primary goal: fewer template instantiations for string overloads of
stride,strideBack,codeLength,isValidUTF, andvalidate.Secondary goal: smaller generated object size.
Object size
Object size is the better bloat signal. The trampolines remain templates (one instantiation per string type), but the large UTF-8 / UTF-16 bodies are now shared non-template
const(C)[]implementations.-vtemplates=list-instancesFocused APIs (DMD summary totals / distinct)
stridestrideBackcodeLengthisValidUTFisValidUTFImplvalidatevalidateImpldecodedecodeImpldecodeFrontdecodeBackbyUTFInterpretation
stride/strideBackstill instantiate once per string type (the public trampoline). Distinct counts therefore barely move. The win is thatstring,char[], andconst(char)[](and the wchar analogs, including static arrays viacast(const(C)[])) share onestrideUTF8/strideUTF16/strideBackUTF8/strideBackUTF16body. That shows up in object size, not in trampoline instantiation counts.codeLength. Matching strings now hit the identitycodeLength(C)(const(C)[] input)(3 encodings) instead of the transcoding range overload. DistinctcodeLengthinstantiations drop by 6; total call sites stay 60 because the driver still asks for every encoding combination.isValidUTF/validate. Public S-templates remain (9 string types). The heavy work moves toisValidUTFImpl/validateImplwith 3 encodings. Extra summary records (+6 overall) are these new impl templates; they replace duplicated string-typed bodies.decode/decodeImplsource is unchanged. Instantiations still drop becausevalidateImpl/isValidUTFImplnow passconst(C)[], so decode is instantiated for three encodings rather than each string qualifier.byUTFis unchanged, as intended.byUTFexperiment that merged two entry points into one function with astatic ifat the front increased object size. Tiny trampolines around a shared implementation is the pattern that actually shrinks generated code.Tests
std/utf.dunittests pass in both modes:Both printed
1 modules passed unittests.Implementation notes (for reviewers)
const(dchar)[]next toconst(char)[]clashes with autodecode (is(S : const dchar[])).cast(const(char)[])/ wchar / dchar, notis(immutable S == immutable C[], C), so static arrays such aschar[4]still compile (buf.stridein encode unittests).!is(S : const char[])and analogs) so they do not duplicate the string body.@safe pure nothrow @nogc. Non-template helpers do not infernothrow; without it,byUTFResult.backfailed under-version=NoAutodecodeStrings.@safe pure(they can throwUTFException).stride/strideBackremain the original S-templates.