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/lib/hgConfig.c src/hg/lib/hgConfig.c index 5cf96c65a68..5ee8c485a4d 100644 --- src/hg/lib/hgConfig.c +++ src/hg/lib/hgConfig.c @@ -2,30 +2,33 @@ * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include <stdio.h> #include "common.h" #include "hgConfig.h" #include "hash.h" #include "cheapcgi.h" #include "portable.h" #include "linefile.h" #include "htmshell.h" #include "customTrack.h" #include <sys/types.h> #include <sys/stat.h> +#if defined(__GLIBC__) +#include <malloc.h> +#endif /* the file to read the global configuration info from */ #define GLOBAL_CONFIG_PATH "." #define GLOBAL_CONFIG_FILE "hg.conf" //#define GLOBAL_CONFIG_FILE "/usr/local/apache/cgi-bin/hg.conf" /* the file to read the user configuration info from, starting at the user's home */ #define USER_CONFIG_FILE ".hg.conf" // forwards static void parseConfigFile(char *filename, int depth); /* the hash holding the config options */ static struct hash* cfgOptionsHash = NULL; static boolean isBrowserCgi() @@ -368,30 +371,50 @@ } void cfgSetMaxMem() /* Check hg.conf for maxMem. If not set, don't limit memory. Otherwise * limit memory usage to that number. */ { char *maxMemStr = NULL; if ((maxMemStr = cfgOption("maxMem")) != NULL) { unsigned long maxMem = atol(maxMemStr); setMemLimit(maxMem); } } +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. hgTracks loads tracks in parallel threads, and every thread grows + * its own pool, so the small default step costs a heavy render more than a + * hundred thousand system calls that do nothing for the reader. Bigger steps + * buy that time back and cost resident memory on the same renders. See + * #38225. */ +{ +#if defined(__GLIBC__) +char *topPadStr = cfgOption("mallocTopPad"); +if (topPadStr != NULL) + { + long topPad = atol(topPadStr); + if (topPad > 0) + mallopt(M_TOP_PAD, topPad); + } +#endif +} + void cfgSetLogCgiVars() /* optionally activate dumping of all CGI variables to stderr log */ { cgiSetMaxLogLen(atoi(cfgOptionDefault("logCgiVarMaxLen", "0"))); } void cfgInitCgi() /* init kent libraries for CGI programs: max RAM, CGI logging, etc*/ { cfgSetLogCgiVars(); cfgSetMaxMem(); if (cfgOptionBooleanDefault("showEarlyErrors", FALSE)) errAbortSetDoContentType(TRUE);