From 1e87b3399ffebad1dcbc49c11c3918d35321e701 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 5 Jan 2023 00:13:50 +0100 Subject: [PATCH] HHH-15976 Further optimise InitializersList to avoid resizing collections --- .../results/internal/InitializersList.java | 131 +++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java index 7589a6f550..d004fff407 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java @@ -7,7 +7,7 @@ package org.hibernate.sql.results.internal; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -22,38 +22,36 @@ import org.hibernate.sql.results.jdbc.spi.RowProcessingState; * Internal helper to keep track of the various * Initializer instances being used during RowReader processing: * different types of initializers need to be invoked in different orders, - * so rather than finding them during each row we keep separated lists - * of initializers defined upfront and then reused for the scope of the whole - * resultset. + * so rather than having to identify the order of initializers again during + * the processing of each row we keep separated lists of initializers defined + * upfront and then reuse these sets for the scope of the whole resultset's + * processing. + * @author Sanne Grinovero */ public final class InitializersList { - private final List initializers; - private final List collectionInitializers; - private final List nonCollectionInitializers; - private final List resolveInstanceFirstInitializers; - private final List resolveInstanceLaterInitializers; + + private final Initializer[] initializers; + private final Initializer[] sortedNonCollectionsFirst; + private final Initializer[] sortedForResolveInstance; private final boolean hasCollectionInitializers; private final Map initializerMap; private InitializersList( - List initializers, - List collectionInitializers, - List nonCollectionInitializers, - List resolveInstanceFirstInitializers, - List resolveInstanceLaterInitializers, + Initializer[] initializers, + Initializer[] sortedNonCollectionsFirst, + Initializer[] sortedForResolveInstance, + boolean hasCollectionInitializers, Map initializerMap) { this.initializers = initializers; - this.collectionInitializers = collectionInitializers; - this.nonCollectionInitializers = nonCollectionInitializers; - this.resolveInstanceFirstInitializers = resolveInstanceFirstInitializers; - this.resolveInstanceLaterInitializers = resolveInstanceLaterInitializers; - this.hasCollectionInitializers = ! collectionInitializers.isEmpty(); + this.sortedNonCollectionsFirst = sortedNonCollectionsFirst; + this.sortedForResolveInstance = sortedForResolveInstance; + this.hasCollectionInitializers = hasCollectionInitializers; this.initializerMap = initializerMap; } @Deprecated //for simpler migration to the new SPI public List asList() { - return initializers; + return Arrays.asList( initializers ); } public Initializer resolveInitializer(final NavigablePath path) { @@ -79,19 +77,13 @@ public final class InitializersList { } public void resolveKeys(final RowProcessingState rowProcessingState) { - for ( Initializer init : nonCollectionInitializers ) { - init.resolveKey( rowProcessingState ); - } - for ( Initializer init : collectionInitializers ) { + for ( Initializer init : sortedNonCollectionsFirst ) { init.resolveKey( rowProcessingState ); } } public void resolveInstances(final RowProcessingState rowProcessingState) { - for ( Initializer init : resolveInstanceFirstInitializers ) { - init.resolveInstance( rowProcessingState ); - } - for ( Initializer init : resolveInstanceLaterInitializers ) { + for ( Initializer init : sortedForResolveInstance ) { init.resolveInstance( rowProcessingState ); } } @@ -101,53 +93,68 @@ public final class InitializersList { } static class Builder { - private List initializers = new ArrayList<>(); - private List collectionInitializers; - private List nonCollectionInitializers; - private List resolveInstanceFirstInitializers; - private List resolveInstanceLaterInitializers; + private ArrayList initializers = new ArrayList<>(); + int nonCollectionInitializersNum = 0; + int resolveFirstNum = 0; public Builder() {} - public void addInitializer(Initializer initializer) { + public void addInitializer(final Initializer initializer) { initializers.add( initializer ); - if ( initializer.isCollectionInitializer() ) { - if ( collectionInitializers == null ) { - collectionInitializers = new ArrayList<>(); - } - collectionInitializers.add( initializer ); + //in this method we perform these checks merely to learn the sizing hints, + //so to not need dynamically scaling collections. + //This implies performing both checks twice but since they're cheap it's preferrable + //to multiple allocations; not least this allows using arrays, which makes iteration + //cheaper during the row processing - which is very hot. + if ( !initializer.isCollectionInitializer() ) { + nonCollectionInitializersNum++; } - else { - if ( nonCollectionInitializers == null ) { - nonCollectionInitializers = new ArrayList<>(); - } - nonCollectionInitializers.add( initializer ); - } - if ( !( initializer instanceof EntityDelayedFetchInitializer ) && ! (initializer instanceof EntitySelectFetchInitializer ) ) { - if ( resolveInstanceFirstInitializers == null ) { - resolveInstanceFirstInitializers = new ArrayList<>(); - } - resolveInstanceFirstInitializers.add( initializer ); - } - else { - if ( resolveInstanceLaterInitializers == null ) { - resolveInstanceLaterInitializers = new ArrayList<>(); - } - resolveInstanceLaterInitializers.add( initializer ); + if ( initializeFirst( initializer ) ) { + resolveFirstNum++; } } - InitializersList build(Map initializerMap) { + private static boolean initializeFirst(final Initializer initializer) { + return !( initializer instanceof EntityDelayedFetchInitializer ) && !( initializer instanceof EntitySelectFetchInitializer ); + } + + InitializersList build(final Map initializerMap) { + final int size = initializers.size(); + final Initializer[] sortedNonCollectionsFirst = new Initializer[size]; + final Initializer[] sortedForResolveInstance = new Initializer[size]; + int nonCollectionIdx = 0; + int collectionIdx = nonCollectionInitializersNum; + int resolveFirstIdx = 0; + int resolveLaterIdx = resolveFirstNum; + final Initializer[] originalSortInitializers = toArray( initializers ); + for ( Initializer initializer : originalSortInitializers ) { + if ( initializer.isCollectionInitializer() ) { + sortedNonCollectionsFirst[collectionIdx++] = initializer; + } + else { + sortedNonCollectionsFirst[nonCollectionIdx++] = initializer; + } + if ( initializeFirst( initializer ) ) { + sortedForResolveInstance[resolveFirstIdx++] = initializer; + } + else { + sortedForResolveInstance[resolveLaterIdx++] = initializer; + } + } + final boolean hasCollectionInitializers = ( nonCollectionInitializersNum != initializers.size() ); return new InitializersList( - initializers, - collectionInitializers == null ? Collections.EMPTY_LIST : collectionInitializers, - nonCollectionInitializers == null ? Collections.EMPTY_LIST : nonCollectionInitializers, - resolveInstanceFirstInitializers == null ? Collections.EMPTY_LIST : resolveInstanceFirstInitializers, - resolveInstanceLaterInitializers == null ? Collections.EMPTY_LIST : resolveInstanceLaterInitializers, + originalSortInitializers, + sortedNonCollectionsFirst, + sortedForResolveInstance, + hasCollectionInitializers, initializerMap ); } + private Initializer[] toArray(final ArrayList initializers) { + return initializers.toArray( new Initializer[initializers.size()] ); + } + } }