I've been doing some performance work on aide, and one result stood out: replacing a hundred analysis findings was allocating somewhere between 180 and 193 MB.
A hundred findings. These are records about things the analyser has spotted in your code, so there will be filenames, line numbers and a bit of explanation in there. Enough to be useful, but hardly an enormous amount of data.
The cost was in how I was putting them into the search index.
aide uses Bleve for full-text search, alongside Bolt for storage. When an analysis runs again, its old findings need replacing so that a search doesn't keep turning up problems you've already fixed. That replacement was committing to the search index one record at a time, building a search segment for each record along the way.
It's an easy enough implementation to arrive at. You have a function that writes a finding, you have a collection of findings, and you call the function for each one. The result is correct. The unnecessary work is hidden inside the function.
I've changed that path to use batches of up to 128 search-index operations. In the benchmark replacing those hundred findings, allocations dropped to about 2.3 MB and the operation took roughly four milliseconds, down from 90–131 milliseconds across the earlier runs.
I'm pleased with that one. It's a useful improvement to a fairly ordinary bit of plumbing, and it doesn't require changing the storage architecture or asking anyone to configure anything.
It does still require care around replacement. Batching the writes is no use if deleted findings remain searchable, or updating one analyser removes another analyser's results. The tests cover those cases, along with replacements that cross the batch boundary. Bolt and Bleve also remain separate stores; this hasn't magically made a write across both of them atomic.
There were a few other places where aide was being unnecessarily thorough. Asking for fifty matching memories used to read all the matches and then discard everything after the first fifty. It now stops when it has enough, after applying the filters. Changed-file analysis also shares content reads and a secrets scanner within the batch, which took another sizeable chunk out of allocations.
These changes are in 0.1.18, though I want to be careful about what the numbers mean. The hundred-findings result is a benchmark of that operation. It doesn't mean aide uses 98% less RAM, or that your coding assistant is about to finish its work thirty times faster.
The broader measurements were much less dramatic. In one controlled comparison with cached grammars, full analysis went from 16.37 seconds to 14.33 seconds. Settled resident memory was a little lower, while peak resident memory was slightly higher. There's native parser memory and mapped database data in the process as well as the Go heap, so the allocation figures don't translate neatly into the number you see in top.
For now, I'm happy to have removed a substantial amount of repeated work from something that runs as the checkout changes. The next thing on the list is another part of findings replacement that still scans and decodes too much of the stored data. I already have a fairly good idea where the next profile needs to start.