HHH-16050 StandardStack optimization using custom array implementation

This commit is contained in:
Marco Belladelli 2023-01-17 17:45:58 +01:00 committed by Sanne Grinovero
parent 01db71c272
commit 8755129648
10 changed files with 246 additions and 92 deletions

View File

@ -68,9 +68,9 @@ public class GraphParser extends GraphLanguageParserBaseVisitor {
private final SessionFactoryImplementor sessionFactory;
private final Stack<GraphImplementor<?>> graphStack = new StandardStack<>();
private final Stack<AttributeNodeImplementor<?>> attributeNodeStack = new StandardStack<>();
private final Stack<SubGraphGenerator> graphSourceStack = new StandardStack<>();
private final Stack<GraphImplementor> graphStack = new StandardStack<>( GraphImplementor.class );
private final Stack<AttributeNodeImplementor> attributeNodeStack = new StandardStack<>( AttributeNodeImplementor.class );
private final Stack<SubGraphGenerator> graphSourceStack = new StandardStack<>(SubGraphGenerator.class);
public GraphParser(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;

View File

@ -6,9 +6,9 @@
*/
package org.hibernate.internal.util.collections;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@ -20,120 +20,95 @@ import java.util.function.Function;
*
* @author Steve Ebersole
* @author Sanne Grinovero
* @author Marco Belladelli
*/
public final class StandardStack<T> implements Stack<T> {
private T[] elements;
private int top = 0;
private ArrayDeque<T> internalStack;
private static final Object NULL_TOKEN = new Object();
private Class<T> type;
public StandardStack() {
public StandardStack(Class<T> type) {
this.type = type;
}
public StandardStack(T initial) {
stackInstanceExpected().addFirst( initial );
public StandardStack(Class<T> type, T initial) {
this( type );
push( initial );
}
@SuppressWarnings("unchecked")
private void init() {
elements = (T[]) Array.newInstance( type, 8 );
type = null;
}
@Override
public void push(T newCurrent) {
T toStore = newCurrent;
if ( newCurrent == null ) {
toStore = (T) NULL_TOKEN;
public void push(T e) {
if ( elements == null ) {
init();
}
stackInstanceExpected().addFirst( toStore );
}
private Deque<T> stackInstanceExpected() {
if ( internalStack == null ) {
//"7" picked to use 8, but skipping the odd initialCapacity method
internalStack = new ArrayDeque<>( 7 );
if ( top == elements.length ) {
grow();
}
return internalStack;
elements[top++] = e;
}
@Override
public T pop() {
return convert( stackInstanceExpected().removeFirst() );
}
private T convert(final Object internalStoredObject) {
if ( internalStoredObject == NULL_TOKEN ) {
return null;
if ( isEmpty() ) {
throw new NoSuchElementException();
}
return (T) internalStoredObject;
T e = elements[--top];
elements[top] = null;
return e;
}
@Override
public T getCurrent() {
if ( internalStack == null ) {
if ( isEmpty() ) {
return null;
}
return convert( internalStack.peekFirst() );
return elements[top - 1];
}
@Override
public T getRoot() {
if ( internalStack == null ) {
if ( isEmpty() ) {
return null;
}
return convert( internalStack.peekLast() );
return elements[0];
}
@Override
public int depth() {
if ( internalStack == null ) {
return 0;
}
return internalStack.size();
return top;
}
@Override
public boolean isEmpty() {
if ( internalStack == null ) {
return true;
}
return internalStack.isEmpty();
return top == 0;
}
@Override
public void clear() {
if ( internalStack != null ) {
internalStack.clear();
for ( int i = 0; i < top; i++ ) {
elements[i] = null;
}
top = 0;
}
@Override
public void visitRootFirst(Consumer<T> action) {
if ( internalStack == null ) {
return;
}
final Iterator<T> iterator = internalStack.descendingIterator();
while ( iterator.hasNext() ) {
action.accept( iterator.next() );
for ( int i = 0; i < top; i++ ) {
action.accept( elements[i] );
}
}
@Override
public <X> X findCurrentFirst(Function<T, X> function) {
if ( internalStack == null ) {
return null;
}
for (T t : internalStack) {
final X result = function.apply(t);
if (result != null) {
return result;
}
}
return null;
}
@Override
public <X,Y> X findCurrentFirstWithParameter(Y parameter, BiFunction<T, Y, X> biFunction) {
if ( internalStack == null ) {
return null;
}
for ( T t : internalStack ) {
final X result = biFunction.apply( t, parameter );
for ( int i = top - 1; i >= 0; i-- ) {
final X result = function.apply( elements[i] );
if ( result != null ) {
return result;
}
@ -141,4 +116,20 @@ public final class StandardStack<T> implements Stack<T> {
return null;
}
@Override
public <X, Y> X findCurrentFirstWithParameter(Y parameter, BiFunction<T, Y, X> biFunction) {
for ( int i = top - 1; i >= 0; i-- ) {
final X result = biFunction.apply( elements[i], parameter );
if ( result != null ) {
return result;
}
}
return null;
}
private void grow() {
final int oldCapacity = elements.length;
final int jump = ( oldCapacity < 64 ) ? ( oldCapacity + 2 ) : ( oldCapacity >> 1 );
elements = Arrays.copyOf( elements, oldCapacity + jump );
}
}

View File

@ -188,7 +188,7 @@ public class QuerySplitter {
private final SqmRoot unmappedPolymorphicFromElement;
private final EntityDomainType<R> mappedDescriptor;
private final SqmCreationContext creationContext;
private final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>();
private final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>( SqmCreationProcessingState.class );
private Map<NavigablePath, SqmPath> sqmPathCopyMap = new HashMap<>();
private Map<SqmFrom, SqmFrom> sqmFromCopyMap = new HashMap<>();

View File

@ -300,8 +300,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
private final Stack<DotIdentifierConsumer> dotIdentifierConsumerStack;
private final Stack<ParameterDeclarationContext> parameterDeclarationContextStack = new StandardStack<>();
private final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>();
private final Stack<ParameterDeclarationContext> parameterDeclarationContextStack = new StandardStack<>( ParameterDeclarationContext.class );
private final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>( SqmCreationProcessingState.class );
private final BasicDomainType<Integer> integerDomainType;
private final JavaType<List<?>> listJavaType;
@ -323,7 +323,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
this.expectedResultType = expectedResultType;
this.creationOptions = creationOptions;
this.creationContext = creationContext;
this.dotIdentifierConsumerStack = new StandardStack<>( new BasicDotIdentifierConsumer( this ) );
this.dotIdentifierConsumerStack = new StandardStack<>(
DotIdentifierConsumer.class,
new BasicDotIdentifierConsumer( this )
);
this.parameterStyle = creationOptions.useStrictJpaCompliance()
? ParameterStyle.UNKNOWN
: ParameterStyle.MIXED;

View File

@ -77,8 +77,8 @@ public class DomainResultCreationStateImpl
private final LegacyFetchResolverImpl legacyFetchResolver;
private final SessionFactoryImplementor sessionFactory;
private final Stack<Function<String, FetchBuilder>> fetchBuilderResolverStack = new StandardStack<>( fetchableName -> null );
private final Stack<Map.Entry<String, NavigablePath>> relativePathStack = new StandardStack<>();
private final Stack<Function> fetchBuilderResolverStack = new StandardStack<>( Function.class, fetchableName -> null );
private final Stack<Map.Entry> relativePathStack = new StandardStack<>( Map.Entry.class );
private Map<String, LockMode> registeredLockModes;
private boolean processingKeyFetches = false;
private boolean resolvingCircularFetch;
@ -356,7 +356,7 @@ public class DomainResultCreationStateImpl
}
final Fetchable fetchable = (Fetchable) identifierMapping;
final FetchBuilder explicitFetchBuilder = fetchBuilderResolverStack
final FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack
.getCurrent()
.apply( fullPath );
DynamicFetchBuilderLegacy fetchBuilderLegacy;
@ -444,7 +444,7 @@ public class DomainResultCreationStateImpl
}
// todo (6.0): figure out if we can somehow create the navigable paths in a better way
final String fullPath = currentEntry.getKey();
FetchBuilder explicitFetchBuilder = fetchBuilderResolverStack
FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack
.getCurrent()
.apply( fullPath );
DynamicFetchBuilderLegacy fetchBuilderLegacy;
@ -473,7 +473,7 @@ public class DomainResultCreationStateImpl
currentEntry.getKey() + "." + partName,
currentEntry.getValue().append( partName )
);
explicitFetchBuilder = fetchBuilderResolverStack
explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack
.getCurrent()
.apply( currentEntry.getKey() );
if ( explicitFetchBuilder == null ) {

View File

@ -468,8 +468,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private List<Map.Entry<OrderByFragment, TableGroup>> orderByFragments;
private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager();
private final Stack<SqlAstProcessingState> processingStateStack = new StandardStack<>();
private final Stack<FromClauseIndex> fromClauseIndexStack = new StandardStack<>();
private final Stack<SqlAstProcessingState> processingStateStack = new StandardStack<>( SqlAstProcessingState.class );
private final Stack<FromClauseIndex> fromClauseIndexStack = new StandardStack<>( FromClauseIndex.class );
/*
* Captures all entity names as which a table group was treated.
* This information is used to prune tables from the table group.
@ -486,11 +486,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private SqmJoin<?, ?> currentlyProcessingJoin;
protected Predicate additionalRestrictions;
private final Stack<Clause> currentClauseStack = new StandardStack<>();
private final Stack<Supplier<MappingModelExpressible<?>>> inferrableTypeAccessStack = new StandardStack<>(
() -> null
);
private final Stack<List<QueryTransformer>> queryTransformers = new StandardStack<>();
private final Stack<Clause> currentClauseStack = new StandardStack<>( Clause.class );
private final Stack<Supplier> inferrableTypeAccessStack = new StandardStack<>( Supplier.class );
private final Stack<List> queryTransformers = new StandardStack<>( List.class );
private boolean inTypeInference;
private boolean inImpliedResultTypeInference;
private Supplier<MappingModelExpressible<?>> functionImpliedResultTypeAccess;
@ -513,6 +511,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
boolean deduplicateSelectionItems) {
super( creationContext.getServiceRegistry() );
this.inferrableTypeAccessStack.push( () -> null );
this.creationContext = creationContext;
this.jpaQueryComplianceEnabled = creationContext
.getSessionFactory()
@ -2074,7 +2073,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
QuerySpec finalQuerySpec = sqlQuerySpec;
for ( QueryTransformer transformer : queryTransformers.getCurrent() ) {
for ( QueryTransformer transformer : (List<QueryTransformer>) queryTransformers.getCurrent() ) {
finalQuerySpec = transformer.transform(
cteContainer,
finalQuerySpec,

View File

@ -269,9 +269,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
private final Set<FilterJdbcParameter> filterJdbcParameters = new HashSet<>();
private final Stack<Clause> clauseStack = new StandardStack<>();
private final Stack<QueryPart> queryPartStack = new StandardStack<>();
private final Stack<Statement> statementStack = new StandardStack<>();
private final Stack<Clause> clauseStack = new StandardStack<>( Clause.class );
private final Stack<QueryPart> queryPartStack = new StandardStack<>( QueryPart.class );
private final Stack<Statement> statementStack = new StandardStack<>( Statement.class );
private final Dialect dialect;
private final Set<String> affectedTableNames = new HashSet<>();

View File

@ -50,7 +50,7 @@ public class DomainResultGraphPrinter {
}
private final StringBuilder buffer;
private final Stack<FetchParent> fetchParentStack = new StandardStack<>();
private final Stack<FetchParent> fetchParentStack = new StandardStack<>( FetchParent.class );
private DomainResultGraphPrinter(String header) {
buffer = new StringBuilder( header + ":" + System.lineSeparator() );

View File

@ -31,7 +31,7 @@ public class LoadContexts {
private static final CoreMessageLogger log = CoreLogging.messageLogger( LoadContexts.class );
private final PersistenceContext persistenceContext;
private final StandardStack<JdbcValuesSourceProcessingState> jdbcValuesSourceProcessingStateStack = new StandardStack<>();
private final StandardStack<JdbcValuesSourceProcessingState> jdbcValuesSourceProcessingStateStack = new StandardStack<>( JdbcValuesSourceProcessingState.class );
public LoadContexts(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;

View File

@ -0,0 +1,161 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.util;
import java.util.NoSuchElementException;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Marco Belladelli
*/
public class StandardStackTest {
@Test
public void testSimpleStackAccess() {
final Stack<Integer> stack = allocateStack( 5 );
assertEquals( 5, stack.depth() );
assertEquals( 0, stack.getRoot() );
assertEquals( 4, stack.getCurrent() );
assertEquals( 4, stack.pop() );
assertEquals( 4, stack.depth() );
}
@Test
public void testNullValues() {
final Stack<Integer> stack = allocateStack( 1 );
stack.push( null );
assertNull( stack.getCurrent() );
assertNull( stack.pop() );
assertNotNull( stack.getCurrent() );
}
@Test
public void testVisitRootFirst() {
final Stack<Integer> stack = allocateStack( 5 );
final int[] i = { 0 };
stack.visitRootFirst( value -> {
assertEquals( i[0], value );
i[0]++;
} );
}
@Test
public void testFindCurrentFirst() {
final Stack<Integer> stack = allocateStack( 5 );
final Integer result = stack.findCurrentFirst( value -> value == 1 ? value : null );
assertEquals( 1, result );
final Integer nullResult = stack.findCurrentFirst( value -> value == 42 ? value : null );
assertNull( nullResult );
}
@Test
public void testFindCurrentFirstWithParameter() {
final Stack<Integer> stack = allocateStack( 5 );
final Integer result = stack.findCurrentFirstWithParameter( 1, this::returnIfEquals );
assertEquals( 1, result );
final Integer nullResult = stack.findCurrentFirstWithParameter( 42, this::returnIfEquals );
assertNull( nullResult );
}
// empty stack tests
@Test
public void testEmptyStackAccess() {
final Stack<Integer> emptyStack = allocateStack( 0 );
assertTrue( emptyStack.isEmpty() );
assertNull( emptyStack.getRoot() );
assertNull( emptyStack.getCurrent() );
assertEquals( 0, emptyStack.depth() );
assertThrows( NoSuchElementException.class, emptyStack::pop );
}
@Test
public void testVisitRootFirstEmpty() {
final Stack<Integer> emptyStack = allocateStack( 0 );
final int[] i = { 0 };
emptyStack.visitRootFirst( value -> i[0]++ );
assertEquals( 0, i[0] ); // lambda function should never have been invoked
}
@Test
public void testFindCurrentFirstEmpty() {
final Stack<Integer> emptyStack = allocateStack( 0 );
final Integer result = emptyStack.findCurrentFirst( value -> value );
assertNull( result );
}
@Test
public void testFindCurrentFirstWithParameterEmpty() {
final Stack<Integer> emptyStack = allocateStack( 0 );
final Integer result = emptyStack.findCurrentFirstWithParameter( 1, (value, param) -> value );
assertNull( result );
}
// cleared stack tests
@Test
public void testClear() {
final Stack<Integer> clearedStack = allocateStack( 42 );
assertEquals( 42, clearedStack.depth() );
assertFalse( clearedStack.isEmpty() );
clearedStack.clear();
assertTrue( clearedStack.isEmpty() );
assertNull( clearedStack.getRoot() );
assertNull( clearedStack.getCurrent() );
assertEquals( 0, clearedStack.depth() );
assertThrows( NoSuchElementException.class, clearedStack::pop );
}
@Test
public void testVisitRootFirstCleared() {
final Stack<Integer> clearedStack = allocateStack( 5 );
clearedStack.clear();
final int[] i = { 0 };
clearedStack.visitRootFirst( value -> i[0]++ );
assertEquals( 0, i[0] ); // lambda function should never have been run
}
@Test
public void testFindCurrentFirstCleared() {
final Stack<Integer> clearedStack = allocateStack( 5 );
clearedStack.clear();
final Integer result = clearedStack.findCurrentFirst( value -> value );
assertNull( result );
}
@Test
public void testFindCurrentFirstWithParameterCleared() {
final Stack<Integer> clearedStack = allocateStack( 5 );
clearedStack.clear();
final Integer result = clearedStack.findCurrentFirstWithParameter( 1, (value, param) -> value );
assertNull( result );
}
// utility functions
private Stack<Integer> allocateStack(int size) {
final Stack<Integer> stack = new StandardStack<>( Integer.class );
for ( int i = 0; i < size; i++ ) {
stack.push( i );
}
return stack;
}
private Integer returnIfEquals(Integer value, Integer param) {
return value.equals( param ) ? value : null;
}
}