arsd 11.1 tagged, cgi.d performance, SerpentOS moves to Rust, trying out reggae

Posted 2023-09-11

Some thoughts about cgi.d future optimizations with api and allocator design, some brief commentary on some snakes, and a long exploration of reggae.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

arsd 11.1

I tagged arsd 11.1 last Friday, bringing the new things discussed a couple weeks ago up live.

Web frameworks benchmark

BTW a new benchmark is out showing the arsd cgi.d as the fastest D web system by a significantly large margin (similarly to the other benchmark): https://web-frameworks-benchmark.netlify.app/result?asc=0&l=d&metric=totalRequestsPerS&order_by=level512

Compared to the top-of-the-line frameworks in other languages though, I come in more like 50% of them in these tests.

Of course, these tests are not very realistic. Adding some get params to a cgi.d test will cut its performance by 20% - arguably due to an api misdesign from its earliest days - but it is there, and I'm sure there's similar cliffs you can fall in the other frameworks, but then when you do other real work in the request instead of printing out a minimal reply that's going to also radically change things and may change these basic test differences into negligibility anyway.

So, it amuses me to win, but let's not read very much into this at all. It likely doesn't matter in real life.

That said, it is sometimes amusing to think about some little efficiencies I could still gain, and the reduction by adding get params is an interesting one. That api misdesign I mentioned is that the parameters are exposed as public immutable associative arrays. This, in theory, forces a very specific implementation - the request must be fully read in the constructor and build infinite lifetime built-in AA's up front.

In practice though, I know I rarely use those specifics. It isn't like I take the address of it, for example, but I do think I've passed it to another assoc array. That's relatively easy to fix though: if I change the type, anyone who did that will see a type error. Heck, I could even alias toAA this; to let the compiler do it in most cases. Anyway, type errors are among the most benign type of breaking changes - yes, it breaks the build, but the fix is easy, again, add a .toAA to that specific spot if you used it and move on.

Most other things can be papered over with operator overloads to keep the api similar. So I think the breakage in changing it to a different type is manageable. (But note it is definite breakage, so perhaps an arsd 12 thing for next year if I do it, unless it is opt in.)

What worries me more is changing the allocation scheme. The built-in AA makes a number of trade offs for its (perhaps overly, it even guarantees you can escape stable pointers to its contents) general purpose mission that are fairly wasteful for cgi.d's param api. So even changing just that alone might provide some notable wins. But I think if I'm breaking things anyway, I might instead prefer to switch the allocation scheme too, to a request-local pool. And if I change that, what about escaping references? This could either cause build massive breakage (trying to force users to move to the scope/dip1000 mechanisms) or worse, frustratingly obscure runtime breakage that only pops up sometimes for some people.

Perhaps a compromise would be to have a GC managed request-local pool behind the mostly-compatible API. It can allocate a block along with, but perhaps separate to (primarily so it can be a NO_SCAN block), the request's Cgi object and strings, etc., come out of there. Small requests - which ought to be the majority - could pull right out of there. Indeed, perhaps it could even use this area directly for socket reads too to reduce copies, but I'm a bit skeptical that this would be worth it. Larger requests can go back to the GC as normal. I'd basically just be a small string optimization pool. The size can be based on the existing maxContentLength argument given to GenericMain, so users can tweak it.

Then, the cgi object has a new method: recyclePool, or something, that you call explicitly in your request handler. This marks it for reuse by the next request coming in this worker thread. The default would still be normal GC work - if you escape these string references beyond the request, no big deal, it will still be immutable and automatically managed as long as it is alive. But then if you mark compatibility with the new scheme by calling the method, it saves a bit of work and avoids contenting the GC's lock by reusing the same memory. If you use care, you can still escape individual references with .idup while recycling the rest, and if you don't use care, an escaped reference will likely pin the whole pool - which can be wasteful (the default will have to be somewhat small) - but not causing mysterious runtime breakage for existing users.

I do recommend you already .idup things that survive beyond the request... and not even expect it to *actually* survive in general anyway, since it is incompatible with the process isolation of the normal cgi model. But I do it myself sometimes too and I know other people sometimes do, so I can't just outright break it, but I think this GC managed, user recycled pool might just work out.

I may do this under a version block in the near term, but no defaults will change until version 12 next year at least.

Taming the Snakes

At DConf, not even two full weeks ago, we heard from a Serpent OS developer about how great D is.

Mere days later, he posted a blog:

https://serpentos.com/blog/2023/09/06/oxidised-moss/

saying the project had already ditched D and gone with Rust.

I'm not surprised, they've been talking about doing this for some time, but lol, another dconf success story. I might write more about this next week; I had some commentary on the chat room about subjective empowerment relating to language choice, but I'll come back to this later.

Reggae Editorial

Speaking of benchmarks and empowerment, I got into talking about the reggae build system with its author, Atila, on the internet chatroom during the dconf week, and since version 0.10 just came out last week too (note that reggae's first public release was April 2015, it isn't especially new despite its version number), I wanted to talk about it briefly here too.

I'm skeptical of the benefit of reggae - it adds significant complication to builds and does not bring significant benefit in any of my tests. Let's take a look (note that a.d is a hello world using the arsd libs and dmdi is my small script to run dmd -i -I/path/to/arsd):

$ time dmdi a.d
real    0m0.324s

$ time dub build real 0m0.442s

$ time ninja real 0m3.474s

OK maybe not fair, I edited the file to add an import after initially running dub run reggae, which took a significant amount of time.

If i do a more benign edit, like just just adding a comment to the code:

# the other two results are unchanged

$ time ninja [2/2] Linking hello

real 0m0.303s

Sure, reggae was faster here... but only by about 20 milliseconds. At the cost of having to write additional configuration files (you can write a reggaefile or make a dub.json and generate the reggaefile from it, but either way, you need to write something and dub run reggae to generate the actual build system - a ninjafile or makefile or whatever - which is then run separately). I, of course, am not a dub user, since I use dmd -i instead. Adding dependencies for me is as simple as importing the module in D and recompiling. By contrast, with dub, you must import it then redundantly add dependencies block to dub.json. I'll grant that dub can download the library too, but downloading a library is trivial - you can just git clone https://github.com/adamdruppe/arsd.git and then git pull to update it at your leisure and everything else just works, so I don't really care about this.

But anyway, even if you already use dub, using reggae adds several seconds to rebuild the metabuild if you change anything. Minimally, this results in several full seconds. But OK, what if you ignore that cost and just rebuild with a warm cache:

# I ran ninja before which ran reggae to rebuild
# then i edited a.d again to invalidate the top level
# of cache so this does something:
$ time ninja
[2/2] Linking hello
real    0m0.751s

$ time dmdi a.d real 0m0.793s

Hey 40 ms this time! 40 fewer milliseconds is technically faster, but still negative return on investment overall.

It would be interesting to test it on the slowest build i have - the ffr bingo web app, which takes over 2 full seconds to rebuild with dmd -i - but it'd take so much config file boilerplate that I don't even want to bother with it; the bingo app has several dependencies, all of which magically just work with dmd -i but require a lot of spam to make dub read it, then it is an open question if reggae will successfully read it (reggae has historically had a lot of bugs in actually reading these config files successfully outside of simple cases - they didn't work for the arsd libs at all for a long time, but it can use those now, so maybe it isn't has bad anymore, just still, the more complex the plumbing, the easier it is to stop up the drain).

But, I am curious enough, I'll suffer through it. Turns out my webtemplate.d module isn't even available on dub (so. much. redundant. boilerplate. on both the user end and the library end, and it limits flexibility of optional dependencies, dub's way of doing it is so much worse than dmd's, sigh.)

But after that and spending several minutes making the config file, I got it building with dub. The first build took almost 7 seconds (yikes!), but subsequent builds, after just editing the main file, took about 2.8 seconds:

$ time dub build
real    0m2.799s

$ time make dmdi bingo.d -J. -g -debug -version=embedded_httpd_hybrid

real 0m2.893s

For comparison, there's my makefile that forwards to dmd -i, that does the simple thing. dub, caching the libraries, saved almost 1/10th of a second for this complete web application.

Now, let's try reggae. I'll show you the whole process here.

$ time dub run reggae
Building package reggae in /home/me/.dub/packages/reggae-0.9.5/reggae/
Performing "debug" build using /home/me/bin/dmd for x86_64.
dub 1.31.1: target for configuration "library" is up to date.
reggae 0.9.5: target for configuration "executable" is up to date.
To force a rebuild of up-to-date targets, run again with --force.
Running ../../../.dub/packages/reggae-0.9.5/reggae/bin/reggae
Reggae        +0s  Writing reggae source files
Reggae    +0.001s  Writing reggae configuration
Reggae    +0.001s  Writing dub configuration
Reggae    +0.226s  Creating dub object
Reggae    +0.226s  Fetching dub packages
Reggae    +0.227s  Fetched dub packages
Reggae    +0.227s      Getting dub build information
Reggae    +0.243s  Getting dub configurations
             Generating test runner configuration 'bingo-test-application' for 'application' (executable).
Reggae    +0.291s  Number of dub configurations: 2
Reggae    +0.291s  Querying dub configuration 'application'
Reggae     +0.33s  Querying dub configuration 'bingo-test-application'
Reggae    +0.372s      Got     dub build information
Reggae    +0.375s  Finished writing dub configuration
Reggae    +0.375s  Creating reggaefile.d from dub information
Reggae    +0.375s  Compiling metabuild binary dcompile
Reggae     +1.31s  Compiling metabuild binary build.o
Reggae    +3.292s  Compiling metabuild binary buildgen
Reggae    +4.133s  Running the created binary to generate the build
Reggae     +4.14s  Build generated

real 0m4.310s

$ time ninja [1/15] Compiling .reggae/objs/bingo.objs/home/me/program/bingo_bingo.o FAILED: .reggae/objs/bingo.objs/home/me/program/bingo_bingo.o .reggae/dcompile --objFile=.reggae/objs/bingo.objs/home/me/program/bingo_bingo.o --depFile=.reggae/objs/bingo.objs/home/me/program/bingo_bingo.o.dep dmd -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.color=/home/me/arsd/color.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.http2=/home/me/arsd/http2.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.sqlite=/home/me/arsd/sqlite.d -mv=arsd.database=/home/me/arsd/database.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.webtemplate=/home/me/arsd/webtemplate.d -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.jsvar=/home/me/arsd/jsvar.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.script=/home/me/arsd/script.d -mv=arsd.jsvar=/home/me/arsd/jsvar.d -mv=arsd.core /home/me/program/bingo/bingo.d(7): Error: module card is in file 'ffr_bingo/card.d' which cannot be read import path[0] = /home/me/program/bingo/testing import path[1] = /home/me/d/dmd2/linux/bin64/../../src/phobos import path[2] = /home/me/d/dmd2/linux/bin64/../../src/druntime/import Error compiling! [2/15] Compiling '.reggae/objs/bingo.objs/__dub_cache__/d...ux-test-application-debug-linux.posix-x86_64-dmd_v2.098.o' FAILED: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-linux.posix-x86_64-dmd_v2.098.o .reggae/dcompile --objFile='.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-linux.posix-x86_64-dmd_v2.098.o' --depFile='.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-linux.posix-x86_64-dmd_v2.098.o'.dep dmd -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d mv=arsd.color=/home/me/arsd/color.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.http2=/home/me/arsd/http2.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.sqlite=/home/me/arsd/sqlite.d -mv=arsd.database=/home/me/arsd/database.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.webtemplate=/home/me/arsd/webtemplate.d -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.jsvar=/home/me/a /home/me/program/bingo/testing/__dub_cache__/bingo/~master/code/bingo-test-application-debug-linux.posix-x86_64-dmd_v2.098.0-A29C4A604C63D02FC6D672E3FB89FCE660BA91E80337121C597398B1ED4C193C/dub_test_root.d(6): Error: module main is in file 'ffr_bingo/main.d' which cannot be read import path[0] = /home/me/program/bingo/testing import path[1] = /home/me/d/dmd2/linux/bin64/../../src/phobos import path[2] = /home/me/d/dmd2/linux/bin64/../../src/druntime/import Error compiling! [4/15] Compiling .reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o FAILED: .reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o .reggae/dcompile --objFile=.reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o --depFile=.reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o.dep dmd -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.color=/home/me/arsd/color.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.http2=/home/me/arsd/http2.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.sqlite=/home/me/arsd/sqlite.d -mv=arsd.database=/home/me/arsd/database.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.webtemplate=/home/me/arsd/webtemplate.d -mv=arsd.cgi=/home/me/arsd/cgi.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.dom=/home/me/arsd/dom.d -mv=arsd.characterencodings=/home/me/arsd/characterencodings.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.jsvar=/home/me/arsd/jsvar.d -mv=arsd.core=/home/me/arsd/core.d -mv=arsd.script=/home/me/arsd/script.d -mv=arsd.jsvar=/home/me/arsd/jsv /home/me/program/bingo/ffr_bingo/tracker.d(7): Error: module main is in file 'ffr_bingo/main.d' which cannot be read import path[0] = /home/me/program/bingo/testing import path[1] = /home/me/d/dmd2/linux/bin64/../../src/phobos import path[2] = /home/me/d/dmd2/linux/bin64/../../src/druntime/import Error compiling! [6/15] Compiling .reggae/objs/bingo.objs/arsd_http2.a ninja: build stopped: subcommand failed.

What a surprise, it failed to even build. Like I said, the more you complicate the plumbing...

but to be fair, that's last week's version of reggae. Let's force dub to update that.

$ rm -r ~/.dub/packages/reggae-0.9.5/
$ time dub run reggae
Package 'reggae' was not found locally but is available online:
---
Description: A build system in D
Version: 0.10.0
---
Do you want to fetch 'reggae' now? [Y/n]: y
Fetching reggae 0.10.0...
Building package reggae in /home/me/.dub/packages/reggae-0.10.0/reggae/
Dependency from unit-threaded:mocks to root package references wrong path: /home/me/.dub/packages/unit-threaded-2.1.2/unit-threaded/ vs. /home/me/.dub/packages/unit-threaded-2.1.2/unit-threaded/subpackages/assertions/

real 0m5.358s

Did that build even succeed? Running it again

$ time dub run reggae .
Expected one or zero arguments.
Run "dub run -h" for more information about the "run" command.

real 0m0.071s

Come on. What a pain in the ass. But I've gone this far and am still curious in the result (even though we're already well past the point where there's any possibility of a profitable return, given the 15 minutes I've sunk into this), let's try updating my dub. OK, it wants to recompile.

$ time dub run reggae -- .
             Building package reggae in /home/me/.dub/packages/reggae/0.10.0/reggae/
    Fetching unit-threaded 2.1.2 (getting selected version)
    Fetching dub 1.34.0 (getting selected version)
    Starting Performing "debug" build using /home/me/bin/dmd for x86_64.
    Building dub 1.34.0: building configuration library
    Building reggae 0.10.0: building configuration executable
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(93,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(121,36): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(153,22): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(382,8): Error: template instance imported!"reggae.options" template imported is not defined, did you
ean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(405,8): Error: template instance imported!"reggae.options" template imported is not defined, did you
ean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(450,8): Error: template instance imported!"reggae.options" template imported is not defined, did you
ean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/common.d(525,8): Error: template instance imported!"reggae.options" template imported is not defined, did you
ean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/backend/package.d(13,8): Error: template instance imported!"reggae.build" template imported is not defined
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/dub.d(21,11): Error: template instance imported!"reggae.config" template imported is not defined
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(85,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(103,30): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(85,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(103,30): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(115,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(160,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(182,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(211,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(241,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(332,8): Error: template instance imported!"reggae.options" template imported is not defined, did you mean
sSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/payload/reggae/rules/d.d(371,30): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
/home/me/.dub/packages/reggae/0.10.0/reggae/src/reggae/dub/interop/dublib.d(121,9): Error: template instance imported!"reggae.options" template imported is not defined
/home/me/.dub/packages/reggae/0.10.0/reggae/src/reggae/json_build.d(45,22): Error: template instance imported!"reggae.options" template imported is not defined, did you mean isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!Range)?
Error /home/me/bin/dmd failed with exit code 1.

real 0m20.127s

For the love of D. I guess I'll try using the dmd-master.

$ time dub run --compiler=dmd-master reggae -- .
             Building package reggae in /home/me/.dub/packages/reggae/0.10.0/reggae/
    Starting Performing "debug" build using dmd-master for x86_64.
    Building dub 1.34.0: building configuration library
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(248,27): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(250,27): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(283,27): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(286,27): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(323,31): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(325,31): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(332,31): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
/home/me/.dub/packages/dub/1.34.0/dub/source/dub/internal/configy/Exceptions.d(335,31): Deprecation: @safe function formatMessage calling formattedWrite
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/write.d(537,34):        which calls std.format.spec.FormatSpec!char.FormatSpec.writeUpToNextSpec!(void delegate(in char[]) @safe).writeUpToNextSpec
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        which wouldn't be @safe because of:
/home/me/d/pull-request-stuff/dmd/generated/linux/release/64/../../../../../phobos/std/format/spec.d(258,33):        scope variable this assigned to non-scope parameter e calling put
    Building reggae 0.10.0: building configuration executable
     Linking reggae

Running ../../../.dub/packages/reggae/0.10.0/reggae/bin/reggae . Reggae +0.038s Creating dub object Reggae +0.038s Fetching dub packages Reggae +0.039s Fetched dub packages Reggae +0.039s Getting dub build information Reggae +0.056s Getting dub configurations Warning Package bingo (configuration "application") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Executable configuration "application" of package bingo defines no main source file, this may cause certain build modes to fail. Add an explicit "mainSourceFile" to he package description to fix this. Warning Package arsd-official:cgi (configuration "embedded_httpd") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:core (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:color_base (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:dom (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:characterencodings (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:http (configuration "with_openssl") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:sqlite (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:database_base (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:webtemplate (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:jsvar (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Warning Package arsd-official:script (configuration "library") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this. Generating test runner configuration 'bingo-test-application' for 'application' (executable). Reggae +0.106s Number of dub configurations: 2 Reggae +0.106s Querying dub configuration 'application' Reggae +0.142s Querying dub configuration 'bingo-test-application' Reggae +0.179s Got dub build information

real 0m34.671s

Wow, about 35 seconds, most of which was spent compiling reggae itself. Maybe I see why Atila thinks builds are too slow: his code builds too slowly. This is the guy who said "you're writing D wrong" in the conference.

OK, but it worked this time. Let's run it

$ time ninja
<snip>
ninja: build stopped: subcommand failed.

f!@#$ing BS. Yeah, it is still broken. Again, dub build works (after wasting several minutes writing a redundant dub.json file, but it worked.). dmd -i just works, no effort required. reggae does not.

And I don't even know how to fix it; there's too many layers of stuff here. The dub config apparently works; dub build make a running program. So is reggae what failed?

But I'm late for another important commitment. Who would have thought just compiling a program would take *checks clock* 30 minutes of aggravation!?! Geeze.

and I'm back. (So much for releasing this article on time!) So, the compile error here says it can't read a file main.d from the project package. Well, my main module is called bingo.d at the top level. Now, I'll grant one of the child modules do import the main module (the main module does more than it should, I confess), so I suppose that must be the problem. Since my preferred build, and even dub too, start from the main module and then find dependencies that way, the main module's location is always specified on the command line so things just work. Apparently, reggae breaks this.

Let me back up a bit. I wanted to keep the dub and reggae spam out of the main directory, so I made a build dir (the reggae docs also suggest this, so it isn't that abnormal) with the following content:

{
        "targetType":"executable",
        "authors": [
                "me"
        ],
        "copyright": "Copyright © 2023, me",
        "dependencies": {
                "arsd-official:color_base": "*",
                "arsd-official:cgi": "*",
                "arsd-official:dom": "*",
                "arsd-official:webtemplate": "*",
                "arsd-official:http": "*",
                "arsd-official:sqlite": "*"
        },
        "stringImportPaths":["../"],
        "sourceFiles": ["../bingo.d"],
        "sourcePaths": ["../"],
        "description": "A minimal D application.",
        "license": "proprietary",
        "name": "bingo"
}

Note that you can see this program online here: https://github.com/adamdruppe/ffr-bingo though I haven't git pushed for a while, you can still get the idea. Note that the makefile just does dmdi bingo.d with a few flags, and remember dmdi is just my local shortcut for dmd -i -Ipath/to/arsd -L-L/path/to/postgres.) The main function is actually in the top-level bingo.d (well, actually, it is mixed in from cgi.d, but the mixin that introduces it is in that bingo.d file.)

But, the source file bingo.d here is meant to emulate that given root on the command line, and then the sourcePaths bring is back to the root so things work. The compiler can easily track this, and dub, when you hold its hand by writing enough spam in dub.json, handles it adequately too. Reggae does not.

OK, I can hack this by adding another dflags thing to dub.json. Whatever.

Nope, now it is complaining other things don't work. It can't find the imports. OK, let's add importPaths alongside sourcePaths.

$ cat dub.json
{
        "targetType":"executable",
        "authors": [
                "me"
        ],
        "copyright": "Copyright © 2023, me",
        "dependencies": {
                "arsd-official:color_base": "*",
                "arsd-official:cgi": "*",
                "arsd-official:dom": "*",
                "arsd-official:webtemplate": "*",
                "arsd-official:http": "*",
                "arsd-official:sqlite": "*"
        },
        "dflags":["-mv=ffr_bingo.main=../bingo.d"],
        "stringImportPaths":["../"],
        "sourceFiles": ["../bingo.d"],
        "sourcePaths": ["../"],
        "importPaths": ["../"],
        "description": "A minimal D application.",
        "license": "proprietary",
        "name": "bingo"
}
 ninja
[0/1] /home/me/.dub/packages/reggae/0.10.0/reggae/bin/reggae
Reggae    +0.036s  Creating dub object
Reggae    +0.036s  Fetching dub packages
Reggae    +0.036s  Fetched dub packages
Reggae    +0.038s      Getting dub build information
Reggae    +0.053s  Getting dub configurations
     Warning Executable configuration "application" of package bingo defines no main source file, this may cause certain build modes to fail. Add an explicit "mainSourceFile" to the package description to fix this.
     Warning Package arsd-official:cgi (configuration "embedded_httpd") defines no import paths, use {"importPaths": ...} or the default package directory structure to fix this.
     <snip useless spam>
Reggae    +0.228s      Got     dub build information
[7/7] Linking bingo
FAILED: bingo
/home/me/bin/dmd -ofbingo -L-lsqlite3 -L-ldl -L--no-as-needed -g '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o' '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-SP1vGoCttipLeBN9Eze9Dw_dub_test_root.o' .reggae/objs/bingo.objs/home/me/program/bingo_bingo.o .reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-Vp2Y3Vk0gpO0vqMrpxHqRQ_dub_test_root.o' '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-QBlbxowDyBorKhLh5gzd1Q_dub_test_root.o' .reggae/objs/bingo.objs/arsd_color.a .reggae/objs/bingo.objs/arsd_http2.a .reggae/objs/bingo.objs/arsd_sqlite.a .reggae/objs/bingo.objs/arsd_database.a .reggae/objs/bingo.objs/arsd_webtemplate.a .reggae/objs/bingo.objs/arsd_cgi.a .reggae/objs/bingo.objs/arsd_dom.a .reggae/objs/bingo.objs/arsd_characterencoding
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-SP1vGoCttipLeBN9Eze9Dw_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-Vp2Y3Vk0gpO0vqMrpxHqRQ_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-QBlbxowDyBorKhLh5gzd1Q_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
ninja: build stopped: subcommand failedReggae    +0.228s      Got     dub build information
[7/7] Linking bingo
FAILED: bingo
/home/me/bin/dmd -ofbingo -L-lsqlite3 -L-ldl -L--no-as-needed -g '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o' '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-SP1vGoCttipLeBN9Eze9Dw_dub_test_root.o' .reggae/objs/bingo.objs/home/me/program/bingo_bingo.o .reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-Vp2Y3Vk0gpO0vqMrpxHqRQ_dub_test_root.o' '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-QBlbxowDyBorKhLh5gzd1Q_dub_test_root.o' .reggae/objs/bingo.objs/arsd_color.a .reggae/objs/bingo.objs/arsd_http2.a .reggae/objs/bingo.objs/arsd_sqlite.a .reggae/objs/bingo.objs/arsd_database.a .reggae/objs/bingo.objs/arsd_webtemplate.a .reggae/objs/bingo.objs/arsd_cgi.a .reggae/objs/bingo.objs/arsd_dom.a .reggae/objs/bingo.objs/arsd_characterencoding
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-SP1vGoCttipLeBN9Eze9Dw_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-Vp2Y3Vk0gpO0vqMrpxHqRQ_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
/usr/bin/ld: .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-QBlbxowDyBorKhLh5gzd1Q_dub_test_root.o:(.data.rel.ro+0x0): multiple definition of `_D13dub_test_root12__ModuleInfoZ'; .reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug--CJPA2dTnyEjKfnsg1ckfQ_dub_test_root.o:(.data.rel.ro+0x0): first defined here
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
ninja: build stopped: subcommand failed..

AAAAAAAAAaaaaaaaaarrrrrrrrrrrrrggggggggghhhhhhhhhhhh! Now what?!?! A linker error?!

And it is about dub_test_root. I don't care about dub test root. Why are you making a dub test root.

Let's try adding mainSourceFile.... ninja ran reggae again, but same failure. ... and dub build is now broken, saying there's "dub_test_root.d conflicts with another module" out the cache. rm -r __dub_cache__ makes dub build work again. Did running reggae even break dub? Yes, it did! Cleaning the cache, then dub build succeeds. Then reggae breaks future runs of dub build until I delete the cache again. It seems to try to generate a unittest file, but I never commanded it to run a test.

This is so frustrating. Could you imagine being a new user having to deal with this crap? I've now spent over an hour just trying to get reggae to work at least equally as well as dub build (which note took about ten minutes to convince to work almost equally as well as dmd -i, which Just Worked in heavenly bliss, filling me with euphoric joy.)

But, I wanna see the timing results! Let's keep trying to fix this.

So, what the heck is dub_test_root.d and why does it insist on being there? dub build doesn't use it. But dub test does, and it complains "only one main allowed". That's understandable (well... sort of, it is silly for it not to just permit the main to be there, since for a long time now the unittest builds can be told to run main or not via a command line switch, so there's no reason to separate it out). It is just some auto-generated main file that lists the modules and runs the test with some boilerplate for betterC (my nemesis strikes again.) But... I don't care. I want to run the program, not its unittests.

Maybe there's some ninja switch that tells it not to run that. But I don't know much of anything about ninja. But whatever, let's just edit those auto-generated files and remove any reference to that unnecessary module.

$ vim build.ninja
$ time ninja
[1/1] Linking bingo

real 0m1.510s

Finally, it generated an executable!!!! It runs too. Glorious.

Now, let's edit the file and see what happens. I'll just remove a comment from the top-level bingo.d file.

$ time ninja
[0/1] /home/me/.dub/packages/reggae/0.10.0/reggae/bin/reggae
Reggae    +0.037s  Creating dub object
Reggae    +0.037s  Fetching dub packages
Reggae    +0.037s  Fetched dub packages
Reggae    +0.038s      Getting dub build information
Reggae    +0.053s  Getting dub configurations
<snip>
[6/6] Linking bingo
FAILED: bingo
<snip same thing again>
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
ninja: build stopped: subcommand failed.

real 0m2.910s

AAAAAAAAAAAHHHHH it recreated that broken file again! But, even it *failing* to build took more time than the successful build with plain old dmd -i. I think we're about to our conclusion, even if it worked, it wouldn't actually save any time.

But, looking back at the link command, it lists that same module... three times. Why would it do that? ...Why would it list it at all? But, if I copy/paste the link command and delete two of those lists of the same thing, it does finally link:

$ time /home/me/bin/dmd -ofbingo -L-lsqlite3 -L-ldl -L--no-as-needed -g .reggae/objs/bingo.objs/home/me/program/bingo_bingo.o .reggae/objs/bingo.objs/home/me/program/bingo/ffr_bingo_card.o '.reggae/objs/bingo.objs/__dub_cache__/bingo/~master/code/bingo-test-application-debug-SP1vGoCttipLeBN9Eze9Dw_dub_test_root.o' .reggae/objs/bingo.objs/arsd_color.a .reggae/objs/bingo.objs/arsd_http2.a .reggae/objs/bingo.objs/arsd_sqlite.a .reggae/objs/bingo.objs/arsd_database.a .reggae/objs/bingo.objs/arsd_webtemplate.a .reggae/objs/bingo.objs/arsd_cgi.a .reggae/objs/bingo.objs/arsd_dom.a .reggae/objs/bingo.objs/arsd_characterencodings.a .reggae/objs/bingo.objs/arsd_script.a .reggae/objs/bingo.objs/arsd_jsvar.a .reggae/objs/bingo.objs/arsd_core.a

real 0m1.494s

The more you complicate the plumbing, the easier it is to stop up the drain. More code = more bugs.

OK, last time, let's try editing the ninja file to remove references to that useless file AND to remove the rule that cause it to rebuild itself.

$ vim build.ninja
$ time ninja
[1/1] Linking bingo

real 0m1.488s

OMG! Let's edit the file, add a comment to bingo.d. Then separately, try card.d editing.

$ vim ../bingo.d
$ time ninja
[3/3] Linking bingo

real 0m2.490s $ vim ../ffr_bingo/card.d $ time ninja [3/3] Linking bingo

real 0m2.495s

OK, we seem to successfully have incremental builds. If I don't edit anything, it quickly says "ninja: no work to do".

Let's edit both files at once:

$ vim ../bingo.d
$ vim ../ffr_bingo/card.d
$ time ninja
real    0m2.509s

Finally, we have something to compare. Remember, my old system did:

$ time make
dmdi bingo.d -J. -g -debug -version=embedded_httpd_hybrid

real 0m2.732s

So, reggae did, in fact, speed up the rebuilds... by about a whole quarter of a second. tbh I don't think the difference between 2.7 and 2.5 seconds warranted all this frustration (and knowing if I add any more dependencies I've gotta go through it all again), but I'll be fair and say it did, in fact, speed things up in all my tests, with improvements ranging from 30ms to 300ms.

It would take thousands of these minimally changed incremental builds to save the time spent setting it up. Sometimes you spend more time now when you can afford it to save less time later when it is more critical, but come on.

Of course, there may be some projects where the change is far more dramatic. I'd suggest you'd get a better return on investment by making the code itself better than throwing more layers of code and hardware at it (notice how this complete web application with database support, an integrated scripting language, and a bunch of other things builds 10x faster than a meta-build system), or perhaps making an alternative build configuration that only brings in what you need instead of a full rebuild (this kind of thing is easy with dmd -i since the compiler does it for you, so long as you show some import discipline), but maybe that's not practical.

Perhaps a fully optimized build with ldc or gdc would see different results too. ldc is unbearably slow:

$ time ldc2i -O bingo.d -J. -g --d-debug --d-version=embedded_httpd_hybrid

real 0m29.572s

So more room for improvement there that maybe, MAYBE, reggae can get, but I don't wanna go through the whole reconfiguration mess to change compilers. (With the command line, it took me about 30 seconds to change the command from dmd to ldc options. With reggae... I don't know, I probably have to regenerate it then fix that broken file again and I'm sick of this. But I rarely do ldc -O builds anyway.)

dub run reggae might be worth trying. But if it fails, just move on, it isn't worth spending more than a minute on. You'll probably never make back your time investment.

Conclusion

I predicted this result. Incremental builds rarely have significant wins in D code for a bunch of reasons. Importing a module and compiling the module often do most of the same work - CTFE can still be run, templates still instantiated. And when you do it all at once, this stuff is reused; it is cached inside the compiler (assuming you have enough RAM, D builds can take gigs in some cases, especially if you're not carefully coding the compile time feature use.). If you do separate compilation, all that is done over again for each module that uses it. You can hide this to some extent with parallel builds, so each CPU is doing the same work as the others, but at the same time so it doesn't appear to stack up to you in the real world, but there's only so much this can do too.

If you really want to make this kind of incremental build work well, you've also gotta generate some kind of interface/index file, perhaps something like I discussed last year, which extracts some of these expensive jobs. This is easier said than done.

Codegen time can add up with optimizations turned on, but I only flip the optimization switch on special occasions; my incremental dev time is all spent with dmd.

It seems counterinutitive that compiling libraries separately doesn't save much time, but nevertheless, that is the reality, as we saw with the first dub build taking over seven seconds, since it built the dependent libraries separately (and serially), and as we saw with the reggae incremental builds. There's much bigger gains to be had elsewhere than in what reggae tries (and often fails) to do.