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/inc/hgConfig.h src/hg/inc/hgConfig.h
index 5e72dd576d1..03d8e56699f 100644
--- src/hg/inc/hgConfig.h
+++ src/hg/inc/hgConfig.h
@@ -1,60 +1,65 @@
 /* Copyright (C) 2012 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #ifndef HGCONFIG_H
 #define HGCONFIG_H
 
 char* cfgOption(char* name);
 /* return the option with the given name, or null if not found */
 
 char* cfgOptionDefault(char* name, char* def);
 /* return the option with the given name or the given default if it doesn't exist */
 
 boolean cfgOptionBooleanDefault(char* name, boolean def);
 /* return the boolean value for option with the given name or the given
  * default if it doesn't exist. Booleans of yes/no/on/off/true/false are
  * recognized */
 
 char *cfgOptionEnv(char *envName, char* name);
 /* get a configuration optional value, from either the environment or the cfg
  * file, with the env take precedence.  Return NULL if not found */
 
 char *cfgOptionEnvDefault(char *envName, char* name, char *def);
 /* get a configuration optional value, from either the environment or the cfg
  * file, with the env take precedence.  Return default if not found */
 
 char *cfgOption2(char *prefix, char *suffix);
 /* Return the option with the given two-part name, formed from prefix.suffix.
  * Return NULL if it doesn't exist. */
 
 char* cfgOptionDefault2(char *prefix, char *suffix, char* def);
 /* Return the option with the given two-part name, formed from prefix.suffix,
  * or the given default if it doesn't exist. */
 
 char *cfgVal(char *name);
 /* Return option with given name.  Squawk and die if it
  * doesn't exist. */
 
 struct slName *cfgNames();
 /* get list of names in config file. slFreeList when finished */
 
 struct slName *cfgNamesWithPrefix();
 /* get list of names in config file with prefix. slFreeList when finished */
 
 struct slName *cfgValsWithPrefix(char* prefix);
 /* get list of values in config file with prefix. slFreeList when finished */
 
 unsigned long cfgModTime();
 /* Return modification time of config file */
 
 void cfgSetMaxMem();
 /* Check hg.conf for maxMem.  If not set, don't limit memory.  Otherwise
  * limit memory usage to that number. */
 
+void cfgSetMallocTopPad();
+/* Check hg.conf for mallocTopPad.  If set to a positive number of bytes, ask
+ * the C library to grow the heap in steps that size instead of its 128 kB
+ * default. */
+
 void cfgSetLogCgiVars();
 /* optionally activate dumping of all CGI variables to stderr log */
 
 void cfgInitCgi();
 /* init kent libraries for CGI programs: max RAM, CGI logging, etc*/
 #endif