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)