6de494f9973ea14ac9610977c1f58ac3aba5c792
braney
  Fri Aug 14 13:06:16 2026 -0700
replaceFieldInPattern: skip fields the pattern never mentions, refs #38094

This runs once per item of every bigBed track that has a mouseOver or URL
pattern, and it looped over every field in the file.  For each field it made two
allocations and then called dyStringSub unconditionally.  dyStringSub copies the
whole string and allocates a new one even when it finds nothing to replace, so a
file with twenty fields paid twenty full rewrites per item to substitute the one
or two fields the pattern actually names.  The braced form was already guarded
with stringIn; the bare form was not.

Guard the bare substitution the same way, stop early once no dollar sign is left
in the string, and keep the two specs on the stack for normal length field names.

Behavior is unchanged.  dyStringSub with an absent pattern returns a byte
identical copy, so the guard only skips work that had no effect.

Measured: about 8% less processor time per page.  Wall clock does not move,
because tracks load in parallel and this work sits in loader threads that were
not on the critical path.  It is worth having for server capacity rather than
for page latency, and it is recorded that way so nobody expects the page to feel
faster.

diff --git src/lib/common.c src/lib/common.c
index 4e01d3168ab..94d04108efd 100644
--- src/lib/common.c
+++ src/lib/common.c
@@ -1830,60 +1830,85 @@
 }
 
 char *replaceFieldInPattern(char *pattern, int fieldCount, char **fieldNames, char **fieldVals)
 /* Given a pattern containing $fieldName or ${fieldName} variable references, replace each
  * variable with the corresponding value from fieldVals.  The ${} form prevents ambiguity
  * when one field name is a prefix of another (e.g. "chrom" vs "chromStart"). */
 {
 int i;
 struct dyString *result = dyStringNew(256), *sub = NULL;
 dyStringAppend(result, pattern);
 for (i = 0; i < fieldCount; ++i)
     {
     if (fieldVals[i] == NULL || fieldNames[i] == NULL)
         continue;
 
+    // Once no dollar sign is left there is nothing for any remaining field to
+    // match, so the rest of the loop would only copy the string to itself.
+    if (strchr(result->string, '$') == NULL)
+        break;
+
     char *field = fieldNames[i];
     int fieldLen = strlen(field);
-    char *bareSpec = needMem(fieldLen + 2);
-    char *bracedSpec = needMem(fieldLen + 4);
+    // Field names are short.  Keep the specs on the stack in the normal case so
+    // this does not do two allocations per field per item.
+    char bareBuf[128], bracedBuf[130];
+    char *bareSpec, *bracedSpec;
+    boolean onHeap = (fieldLen + 4 > sizeof(bracedBuf));
+    if (onHeap)
+        {
+        bareSpec = needMem(fieldLen + 2);
+        bracedSpec = needMem(fieldLen + 4);
+        }
+    else
+        {
+        bareSpec = bareBuf;
+        bracedSpec = bracedBuf;
+        }
     *bareSpec = '$';
     *bracedSpec = '$';
     bracedSpec[1] = '{';
     strcpy(bareSpec + 1, field);
     strcpy(bracedSpec + 2, field);
     bracedSpec[fieldLen + 2] = '}';
     bracedSpec[fieldLen + 3] = '\0';
 
     if (stringIn(bracedSpec, result->string))
         {
         sub = dyStringSub(result->string, bracedSpec, fieldVals[i]);
         dyStringFree(&result);
         result = sub;
         sub = NULL;
         }
     // the user may have both a ${} enclosed instance and a non-enclosed one
     // also note that if the value substituted above is the field name itself with
     // a leading $, then we will substitute again
+    // dyStringSub copies the whole string even when it finds nothing, so only
+    // call it when there is something to replace.
+    if (stringIn(bareSpec, result->string))
+        {
         sub = dyStringSub(result->string, bareSpec, fieldVals[i]);
-
         dyStringFree(&result);
-    freeMem(bareSpec);
-    freeMem(bracedSpec);
         result = sub;
         sub = NULL;
         }
+    if (onHeap)
+        {
+        freeMem(bareSpec);
+        freeMem(bracedSpec);
+        }
+    }
 return dyStringCannibalize(&result);
 }
 
 int strSwapStrs(char *string, int sz,char *oldStr, char *newStr)
 /* Swaps all occurrences of the old with the new in string. Need not be same size
    Swaps in place but restricted by sz.  Returns count of swaps or -1 for sz failure. */
 {
 // WARNING: called at low level, so no errors allowed.
 int count = 0;
 char *p=NULL;
 for(p=strstr(string,oldStr);p!=NULL;p=strstr(p+strlen(oldStr),oldStr))
     count++;
 if (count == 0)
     return 0;
 if((strlen(string)+(count*(strlen(newStr) - strlen(oldStr))))>=sz)