Skip to content

perf(bigquery): [T1-17] eliminate unconditional HashMap allocations and dead STRUCT pass in QueryParameterValue - #21

Open
lqiu96 wants to merge 1 commit into
mainfrom
perf/bq-t1-17-query-parameter-value
Open

lqiu96 wants to merge 1 commit into
mainfrom
perf/bq-t1-17-query-parameter-value

Conversation

@lqiu96

@lqiu96 lqiu96 commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Summary & Bottleneck

QueryParameterValue and Range perform several avoidable allocations during parameter construction, wire serialization (toValuePb and toTypePb), and wire deserialization (fromPb):

  • QueryParameterValue.fromPb unconditionally allocates Map<String, QueryParameterType> parameterTypes = new HashMap<>() on every recursive call, including primitive scalars (INT64, STRING, BOOL, etc.) and ARRAY elements where parameterTypes is never used.
  • When type == StandardSQLTypeName.STRUCT, QueryParameterValue.fromPb iterates typePb.getStructTypes() twice. The first pass allocates a HashMap and $F$ placeholder QueryParameterValue objects and passes them to valueBuilder.setStructTypes(structTypes). Immediately afterward, for every non-null struct, valueBuilder.setStructValues(structValues) calls setStructTypes(ImmutableMap.copyOf(structValues)), completely overwriting the map and all $F$ placeholder QueryParameterValue objects constructed in the first pass.
  • QueryParameterValue.Builder.setStructValues calls ImmutableMap.copyOf(structValues) twice on the same input map instead of sharing a single ImmutableMap instance between structTypes and structValues.
  • QueryParameterValue.array(T[], StandardSQLTypeName) allocates new ArrayList<>() with default capacity 10 instead of array.length.
  • QueryParameterValue.toValuePb() and Range.getValues() call Range.getStart() and Range.getEnd() twice each. Every call to Range.getStart() or Range.getEnd() allocates a new FieldValue wrapper over getStartInner() or getEndInner().
  • QueryParameterValue.validateTimestamp(String) allocates a regex Matcher for ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN (\.(\d{10,})(?:\D|$)) on every timestamp string parameter, even when the fractional second has 9 or fewer digits.

Changes

  • In java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java:
    • Allocate parameterTypes only inside the non-null StandardSQLTypeName.STRUCT branch of fromPb, and only execute the placeholder structTypes loop when valuePb == null || valuePb.getStructValues() == null.
    • Pre-size collections in fromPb, toTypePb, and array(T[], StandardSQLTypeName).
    • Call ImmutableMap.copyOf(structValues) once in Builder.setStructValues and pass the resulting ImmutableMap to both setStructTypes and setStructValuesInner.
    • Read getRangeValues().getStartInner() and getEndInner() directly in toValuePb() instead of allocating 4 FieldValue wrappers via getStart() and getEnd().
    • Guard ISO8601_TIMESTAMP_HIGH_PRECISION_PATTERN.matcher(timestamp) in validateTimestamp with hasHighPrecisionFraction(timestamp) so timestamps with 9 or fewer fractional digits skip Matcher allocation.
  • In java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Range.java, read getStartInner() and getEndInner() directly in getValues().
  • In java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java, assert that structValues and structTypes share the same ImmutableMap instance and verify fromPb on null STRUCT values.

Expected Impact

  • Eliminates 1 HashMap allocation per scalar, array element, and struct field in QueryParameterValue.fromPb.
  • Eliminates $F$ dead AutoValue_QueryParameterValue allocations, 1 HashMap, and 1 duplicate ImmutableMap.copyOf per non-null STRUCT parameter.
  • Eliminates 4 FieldValue allocations per RANGE parameter during toValuePb() and Range.getValues().
  • Eliminates regex Matcher allocation for all standard (up to nanosecond precision) timestamp string parameters.

Verification

  • Ran unit tests in QueryParameterValueTest and RangeTest:
    mvn test -pl java-bigquery/google-cloud-bigquery -Dtest=QueryParameterValueTest,RangeTest -Dclirr.skip=true -Denforcer.skip=true -Dflatten.skip=true -Dcheckstyle.skip=true -Dfmt.skip=true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant