[ 
https://issues.apache.org/jira/browse/CALCITE-7805?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18118575#comment-18118575
 ] 

Julian Hyde commented on CALCITE-7805:
--------------------------------------

I stand by the word "slop". As I explained in my response to your email, the 
sheer volume of the text in the Jira cases and GitHub PRs makes it difficult 
for a reviewer to respond to your contribution.

> Reduce temporary object allocation when validating large string ARRAYs to 
> lower GC pressure
> -------------------------------------------------------------------------------------------
>
>                 Key: CALCITE-7805
>                 URL: https://issues.apache.org/jira/browse/CALCITE-7805
>             Project: Calcite
>          Issue Type: Improvement
>          Components: core
>    Affects Versions: 1.42.0
>            Reporter: Frank Chen
>            Priority: Major
>              Labels: pull-request-available
>
> CALCITE-7782 and [PR #5263|https://github.com/apache/calcite/pull/5263] 
> eliminated quadratic operand-list copying while validating large ARRAY and 
> MAP constructors. That correction allows a 1,000,000-element string ARRAY to 
> complete instead of running for more than 20 minutes.
> After that correction, validation is linear, but planning still creates many 
> temporary objects for every string literal. In the benchmark below, planning 
> a 1,000,000-element string IN expression allocates approximately 15.4 GB 
> during one operation and takes approximately 7.8 seconds.
> The allocation values below are cumulative allocation during one planning 
> operation, not retained memory or peak heap usage.
> h2. Reproducer
> Apache Druid exposes the remaining allocation through a large string IN 
> predicate:
> {code:sql}
> SELECT string1
> FROM foo
> WHERE string1 IN ('1', '2', ..., '1000000')
> {code}
> Druid rewrites the predicate to an expression containing a string ARRAY:
> {code}
> SCALAR_IN_ARRAY(string1, ARRAY['1', '2', ..., '1000000'])
> {code}
> The benchmark is {{InPlanningBenchmark.queryStringInSqlPlanOnly}}.
> Parameters:
> * {{inSubQueryThreshold = 2147483647}}
> * {{rowsPerSegment = 500000}}
> * 2 JMH forks
> * 2 one-second warmup iterations per fork
> * 5 one-second measurement iterations per fork
> * GC profiler enabled
> * Temurin 25.0.4.1
> The Calcite baseline is commit {{38413ece6}}, which includes the merged 
> CALCITE-7782 change.
> h2. Remaining allocation sources
> Profiling identified five independent allocation paths.
> h3. 1. Generated CAST calls repeat operator resolution
> ARRAY validation creates generated CAST calls when an operand type differs 
> from the common element type. The CAST node already contains the built-in 
> CAST operator, but its validated type is not registered immediately. A later 
> {{deriveType}} call constructs temporary operand and argument-type lists and 
> searches the operator table for the same CAST operator.
> The generated CAST can instead be validated directly through its existing 
> operator, preserving operand validation and validated-type registration while 
> avoiding redundant routine lookup.
> h3. 2. Unchanged charset and collation create equivalent types
> During least-restrictive character-type inference, 
> {{BasicSqlType.createWithCharsetAndCollation}} may be called with the charset 
> and collation already present on the immutable type. The current 
> implementation creates another {{BasicSqlType}} and charset wrapper before 
> canonicalization returns the original canonical type. The method can return 
> the same instance when these attributes are unchanged.
> h3. 3. Canonicalization occurs after temporary type construction
> {{RelDataTypeFactoryImpl}} has a global weak canonical-type interner, but it 
> accepts an already constructed type. It prevents duplicate types from being 
> retained but does not prevent temporary {{BasicSqlType}}, charset wrapper, 
> and digest allocation.
> A small bounded front cache can reuse the most recently created base and 
> decorated type for each {{SqlTypeName}} before constructing another type. The 
> existing global weak interner remains authoritative.
> h3. 4. SQL and Rex visitors eagerly copy unchanged operands
> {{SqlShuttle}} and {{RexShuttle}} copy operand arrays or lists before knowing 
> whether any visited child changes. Read-only traversals of a large literal 
> ARRAY therefore allocate replacement operand collections and later return the 
> original node.
> The visitors can use copy-on-write behavior and allocate an operand 
> collection only after the first child changes. Explicit unconditional-copy 
> behavior and immutable-result contracts remain unchanged.
> h3. 5. String literal digest generation creates a temporary SQL string
> {{RexLiteral}} asks {{NlsString}} to build a complete escaped SQL string and 
> then copies that string into the destination {{StringBuilder}}. {{NlsString}} 
> can provide an overload that writes directly into the caller's builder, while 
> the existing string-returning method delegates to the same implementation.
> h2. Combined benchmark
> The changes were measured together because their allocation reductions 
> overlap and isolated percentages must not be added.
> || String literals || CALCITE-7782 baseline || All proposed changes || 
> Allocation saved ||
> | 100,000 | 1.522 GB/op | 0.445 GB/op | 1.077 GB/op (70.8%) |
> | 1,000,000 | 15.418 GB/op | 4.556 GB/op | 10.862 GB/op (70.5%) |
> The benchmark uses ordered numeric strings such as {{'1'}}, {{'2'}}, and 
> {{'10'}}. The bounded type cache benefits from consecutive literals with the 
> same character length. Fixed-width strings should have similar locality 
> regardless of value order, while arbitrary strings with frequently 
> alternating lengths may show a smaller improvement.
> h2. Proposed delivery
> The implementation is split into five independently reviewable sub-tasks:
> # [CALCITE-7806|https://issues.apache.org/jira/browse/CALCITE-7806] - Avoid 
> resolving generated CAST calls during ARRAY validation.
> # [CALCITE-7807|https://issues.apache.org/jira/browse/CALCITE-7807] - Reuse 
> unchanged SQL types during least-restrictive inference.
> # [CALCITE-7808|https://issues.apache.org/jira/browse/CALCITE-7808] - Cache 
> recently created and decorated SQL types.
> # [CALCITE-7809|https://issues.apache.org/jira/browse/CALCITE-7809] - Avoid 
> copying unchanged SQL and Rex operands.
> # [CALCITE-7810|https://issues.apache.org/jira/browse/CALCITE-7810] - Write 
> Rex string literal digests directly.
> Each sub-task links its corresponding pull request and can be reviewed and 
> tested independently.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to