d2a2afcd6c5c19257f8ed2f746c3f94474f4ab78
braney
  Tue Sep 1 13:37:34 2026 -0700
hgTracks: let hg.conf set how big a step malloc takes, refs #38225

hgTracks loads tracks in parallel threads, and each thread grows its own
memory pool in the 128 kB steps glibc uses by default.  A heavy render asks
the kernel to enlarge a pool about 133,000 times, work that does nothing for
the reader.

The new hg.conf setting mallocTopPad is a number of bytes.  When it is set,
cfgSetMallocTopPad() passes it to mallopt(M_TOP_PAD) before anything else in
main() has a chance to allocate, so the heap grows in that size step instead.
Absent or zero, glibc is left alone and nothing changes.

Measured with the #38094 harness over eight sessions and two positions, at
32 MB: renders take 0.926 of the time they did, against a noise floor of
1.006.  With the setting absent the same build measures 1.004, inside that
noise floor.  Through apache on a sandbox it measures 0.906.  The cost is
about 65 MB more resident memory on a heavy session and 8 to 20 MB on an
ordinary view, where it also saves no measurable time; the saving and the
cost are the same effect and appear together.  The rendered image is byte
identical either way.

Off by default, so a server opts in.

diff --git src/hg/hgTracks/renderMain.c src/hg/hgTracks/renderMain.c
index 71ef4e7b212..362089b3054 100644
--- src/hg/hgTracks/renderMain.c
+++ src/hg/hgTracks/renderMain.c
@@ -6,30 +6,31 @@
 
 #include "common.h"
 #include "linefile.h"
 #include "hash.h"
 #include "htmshell.h"
 #include "options.h"
 #include "errAbort.h"
 #include "portable.h"
 #include "cheapcgi.h"
 #include "ra.h"
 #include "hdb.h"
 #include "net.h"
 #include "hgTracks.h"
 #include "imageV2.h"
 #include "botDelay.h"
+#include "hgConfig.h"
 
 static void usage()
 /* Print out usage and exit - just temporary. */
 {
 errAbort(
 "hgRenderTracks - execute enough of browser to render a list of tracks\n"
 "usage:\n"
 "      hgTrackRender track.ra cart.ra output.ps\n"
 "where track.ra is a trackDb.ra entry flattened out to include stuff inherited from parent\n"
 "and cart.ra is a file with the cart settings.  Both .ra files should just have a single stanza\n"
 );
 }
 
 static struct optionSpec options[] = {
    {NULL, 0},
@@ -102,30 +103,34 @@
 verboseTime(2, "After load");
 
 /* Initialize global image box. */
 int sideSliceWidth  = 0;   // Just being explicit
 if (withLeftLabels)
     sideSliceWidth   = (insideX - gfxBorder*3) + 2;
 theImgBox = imgBoxStart(database,chromName,winStart,winEnd,(!revCmplDisp),sideSliceWidth,tl.picWidth);
 makeActiveImage(trackList, NULL);
 verboseTime(2,"After makeActiveImage");
 }
 
 boolean issueBotWarning;
 
 int main(int argc, char *argv[])
 {
+// First, before anything has a chance to allocate: read hg.conf and set how
+// big a step the C library takes when it grows the heap.  refs #38225
+cfgSetMallocTopPad();
+
 cgiSpoof(&argc, argv);
 if(argc == 1)
     {
     enteredMainTime = clock1000();
     issueBotWarning = earlyBotCheck(enteredMainTime, "hgRenderTracks", delayFraction, 0, 0, "html");
     // CGI call
 
     // htmlPushEarlyHandlers(); XXXX do I need to do this?
 
     hPrintDisable();
     oldVars = hashNew(10);
     struct cart *cart = cartForSession(hUserCookie(), excludeVars, oldVars);
 
     // setup approriate CGI variables which tell hgTracks code what to do.
     cartSetBoolean(cart, "hgt.trackImgOnly", TRUE);