HHH-18271 Avoid mega-morphic callsites for equals/hashCode with known types

This commit is contained in:
Christian Beikov 2024-08-02 12:59:57 +02:00 committed by Steve Ebersole
parent 850a2a0753
commit 55702e458b
35 changed files with 197 additions and 36 deletions

View File

@ -26,7 +26,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class CollectionKey implements Serializable {
private final String role;
private final Object key;
private final Type keyType;
private final @Nullable Type keyType;
private final SessionFactoryImplementor factory;
private final int hashCode;
@ -34,7 +34,7 @@ public final class CollectionKey implements Serializable {
this(
persister.getRole(),
key,
persister.getKeyType(),
persister.getKeyType().getTypeForEqualsHashCode(),
persister.getFactory()
);
}
@ -42,7 +42,7 @@ public final class CollectionKey implements Serializable {
private CollectionKey(
String role,
@Nullable Object key,
Type keyType,
@Nullable Type keyType,
SessionFactoryImplementor factory) {
this.role = role;
if ( key == null ) {
@ -58,7 +58,7 @@ public final class CollectionKey implements Serializable {
private int generateHashCode() {
int result = 17;
result = 37 * result + role.hashCode();
result = 37 * result + keyType.getHashCode( key, factory );
result = 37 * result + ( keyType == null ? key.hashCode() : keyType.getHashCode( key, factory ) );
return result;
}
@ -90,7 +90,7 @@ public final class CollectionKey implements Serializable {
final CollectionKey that = (CollectionKey) other;
return that.role.equals( role )
&& ( this.key == that.key ||
keyType.isEqual( this.key, that.key, factory ) );
keyType == null ? this.key.equals( that.key ) : keyType.isEqual( this.key, that.key, factory ) );
}
@Override

View File

@ -14,6 +14,7 @@ import java.io.Serializable;
import org.hibernate.AssertionFailure;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.Type;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -59,7 +60,8 @@ public final class EntityKey implements Serializable {
int result = 17;
final String rootEntityName = persister.getRootEntityName();
result = 37 * result + rootEntityName.hashCode();
result = 37 * result + persister.getIdentifierType().getHashCode( identifier, persister.getFactory() );
final Type identifierType = persister.getIdentifierType().getTypeForEqualsHashCode();
result = 37 * result + ( identifierType == null ? identifier.hashCode() : identifierType.getHashCode( identifier, persister.getFactory() ) );
return result;
}
@ -99,8 +101,10 @@ public final class EntityKey implements Serializable {
}
private boolean sameIdentifier(final EntityKey otherKey) {
return this.identifier == otherKey.identifier ||
persister.getIdentifierType().isEqual( otherKey.identifier, this.identifier, persister.getFactory() );
final Type identifierType;
return this.identifier == otherKey.identifier || (
(identifierType = persister.getIdentifierType().getTypeForEqualsHashCode()) == null && identifier.equals( otherKey.identifier )
|| identifierType != null && identifierType.isEqual( otherKey.identifier, this.identifier, persister.getFactory() ) );
}
private boolean samePersistentType(final EntityKey otherKey) {

View File

@ -27,13 +27,14 @@ import org.hibernate.query.sqm.CastType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Convenience base class for {@link BasicType} implementations.
* Packages a {@link JavaType} with a {@link JdbcType}.
@ -50,7 +51,7 @@ public abstract class AbstractStandardBasicType<T>
private final ValueBinder<T> jdbcValueBinder;
private final ValueExtractor<T> jdbcValueExtractor;
private final JdbcLiteralFormatter<T> jdbcLiteralFormatter;
private final AbstractClassJavaType<T> javaTypeAsAbstractClassJavaType;
private final @Nullable Type typeForEqualsHashCode;
private final Class javaTypeClass;
private final MutabilityPlan<T> mutabilityPlan;
private final Comparator<T> javatypeComparator;
@ -68,13 +69,7 @@ public abstract class AbstractStandardBasicType<T>
this.javaTypeClass = javaType.getJavaTypeClass();
this.mutabilityPlan = javaType.getMutabilityPlan();
this.javatypeComparator = javaType.getComparator();
//This is a dispatch optimisation to avoid megamorphic invocations on the most common type:
if ( javaType instanceof AbstractClassJavaType ) {
this.javaTypeAsAbstractClassJavaType = (AbstractClassJavaType) javaType;
}
else {
this.javaTypeAsAbstractClassJavaType = null;
}
this.typeForEqualsHashCode = javaType.useObjectEqualsHashCode() ? null : this;
}
@Override
@ -98,13 +93,7 @@ public abstract class AbstractStandardBasicType<T>
}
public T fromString(CharSequence string) {
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
if ( type != null ) {
return type.fromString( string );
}
else {
return javaType.fromString( string );
}
return javaType.fromString( string );
}
protected MutabilityPlan<T> getMutabilityPlan() {
@ -196,25 +185,19 @@ public abstract class AbstractStandardBasicType<T>
else if ( one == null || another == null ) {
return false;
}
else if ( typeForEqualsHashCode == null ) {
return one.equals( another );
}
else {
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
if ( type != null ) {
//Optimize for the most common case: avoid the megamorphic call
return type.areEqual( (T) one, (T) another );
}
else {
return javaType.areEqual( (T) one, (T) another );
}
return javaType.areEqual( (T) one, (T) another );
}
}
@Override
@SuppressWarnings("unchecked")
public int getHashCode(Object x) {
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
if ( type != null ) {
//Optimize for the most common case: avoid the megamorphic call
return type.extractHashCode( (T) x );
if ( typeForEqualsHashCode == null ) {
return x.hashCode();
}
else {
return javaType.extractHashCode( (T) x );
@ -226,6 +209,11 @@ public abstract class AbstractStandardBasicType<T>
return getHashCode( x );
}
@Override
public @Nullable Type getTypeForEqualsHashCode() {
return typeForEqualsHashCode;
}
@Override
@SuppressWarnings("unchecked")
public final int compare(Object x, Object y) {

View File

@ -219,6 +219,15 @@ public interface Type extends Serializable {
*/
int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException;
/**
* The type to use for {@code equals()} and {@code hashCode()} computation.
* When {@code null}, use {@link Object#equals(Object)} and {@link Object#hashCode()}.
* This is useful to avoid mega-morphic callsites.
*/
default @Nullable Type getTypeForEqualsHashCode() {
return this;
}
/**
* Perform a {@link java.util.Comparator}-style comparison of the given values.
*

View File

@ -47,6 +47,11 @@ public class BooleanJavaType extends AbstractClassJavaType<Boolean> implements
stringValueFalse = String.valueOf( characterValueFalse );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Boolean value) {
return value == null ? null : value.toString();

View File

@ -31,6 +31,11 @@ public class ByteJavaType extends AbstractClassJavaType<Byte>
super( Byte.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Byte value) {
return value == null ? null : value.toString();

View File

@ -25,6 +25,11 @@ public class CharacterJavaType extends AbstractClassJavaType<Character> implemen
super( Character.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Character value) {
return value.toString();

View File

@ -22,6 +22,11 @@ public class ClassJavaType extends AbstractClassJavaType<Class> {
super( Class.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(Class value) {
return value.getName();
}

View File

@ -24,6 +24,11 @@ public class CurrencyJavaType extends AbstractClassJavaType<Currency> {
super( Currency.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Currency value) {
return value.getCurrencyCode();

View File

@ -35,6 +35,11 @@ public class DoubleJavaType extends AbstractClassJavaType<Double> implements
return DoubleJdbcType.INSTANCE;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Double value) {
return value == null ? null : value.toString();

View File

@ -52,6 +52,11 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
.getDescriptor( context.getPreferredSqlTypeCodeForDuration() );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Duration value) {
if ( value == null ) {

View File

@ -81,6 +81,11 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
return getJavaTypeClass().getEnumConstants().length > 128;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(T value) {
return value == null ? "<null>" : value.name();

View File

@ -34,6 +34,11 @@ public class FloatJavaType extends AbstractClassJavaType<Float> implements Primi
return indicators.getJdbcType( SqlTypes.FLOAT );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Float value) {
return value == null ? null : value.toString();

View File

@ -28,6 +28,11 @@ public class InetAddressJavaType extends AbstractClassJavaType<InetAddress> {
super( InetAddress.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(InetAddress value) {
return value == null ? null : value.toString();

View File

@ -69,6 +69,11 @@ public class InstantJavaType extends AbstractTemporalJavaType<Instant>
return context.getJdbcType( context.getPreferredSqlTypeCodeForInstant() );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Instant value) {
return DateTimeFormatter.ISO_INSTANT.format( value );

View File

@ -31,6 +31,11 @@ public class IntegerJavaType extends AbstractClassJavaType<Integer>
super( Integer.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Integer value) {
return value == null ? null : value.toString();

View File

@ -223,6 +223,16 @@ public interface JavaType<T> extends Serializable {
return Objects.deepEquals( one, another );
}
/**
* Whether to use {@link Object#equals(Object)} and {@link Object#hashCode()}
* or {@link #areEqual(Object, Object)} and {@link #extractHashCode(Object)}
* for objects of this java type.
* This is useful to avoid mega-morphic callsites.
*/
default boolean useObjectEqualsHashCode() {
return false;
}
/**
* Extract a loggable representation of the given value.
*

View File

@ -60,6 +60,11 @@ public class LocalDateJavaType extends AbstractTemporalJavaType<LocalDate> {
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(LocalDate value) {
return DateTimeFormatter.ISO_LOCAL_DATE.format( value );

View File

@ -60,6 +60,11 @@ public class LocalDateTimeJavaType extends AbstractTemporalJavaType<LocalDateTim
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(LocalDateTime value) {
return DateTimeFormatter.ISO_DATE_TIME.format( value );

View File

@ -65,6 +65,11 @@ public class LocalTimeJavaType extends AbstractTemporalJavaType<LocalTime> {
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(LocalTime value) {
return DateTimeFormatter.ISO_LOCAL_TIME.format( value );

View File

@ -31,6 +31,11 @@ public class LocaleJavaType extends AbstractClassJavaType<Locale> {
super( Locale.class, ImmutableMutabilityPlan.instance(), LocaleComparator.INSTANCE );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(Locale value) {
return value.toString();
}

View File

@ -31,6 +31,11 @@ public class LongJavaType extends AbstractClassJavaType<Long>
super( Long.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Long value) {
return value == null ? null : value.toString();

View File

@ -21,6 +21,11 @@ public class ObjectJavaType extends AbstractClassJavaType<Object> {
super( Object.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
//noinspection unchecked

View File

@ -81,6 +81,11 @@ public class OffsetDateTimeJavaType extends AbstractTemporalJavaType<OffsetDateT
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(OffsetDateTime value) {
return ISO_OFFSET_DATE_TIME.format( value );

View File

@ -64,6 +64,11 @@ public class OffsetTimeJavaType extends AbstractTemporalJavaType<OffsetTime> {
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(OffsetTime value) {
return DateTimeFormatter.ISO_OFFSET_TIME.format( value );

View File

@ -30,6 +30,11 @@ public class ShortJavaType extends AbstractClassJavaType<Short>
super( Short.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Short value) {
return value == null ? null : value.toString();

View File

@ -32,6 +32,11 @@ public class StringJavaType extends AbstractClassJavaType<String> {
super( String.class );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(String value) {
return value;
}

View File

@ -31,6 +31,11 @@ public class TimeZoneJavaType extends AbstractClassJavaType<TimeZone> {
super( TimeZone.class, ImmutableMutabilityPlan.instance(), TimeZoneComparator.INSTANCE );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(TimeZone value) {
return value.getID();
}

View File

@ -34,6 +34,11 @@ public class UUIDJavaType extends AbstractClassJavaType<UUID> {
return context.getJdbcType( context.getPreferredSqlTypeCodeForUuid() );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(UUID value) {
return ToStringTransformer.INSTANCE.transform( value );
}

View File

@ -32,6 +32,11 @@ public class UrlJavaType extends AbstractClassJavaType<URL> {
return context.getJdbcType( SqlTypes.VARCHAR );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(URL value) {
return value.toExternalForm();
}

View File

@ -30,6 +30,11 @@ public class YearJavaType extends AbstractClassJavaType<Year> {
return context.getJdbcType( Types.INTEGER );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(Year value) {
return value == null ? null : value.format( FORMATTER );

View File

@ -31,6 +31,11 @@ public class ZoneIdJavaType extends AbstractClassJavaType<ZoneId> {
return indicators.getJdbcType( Types.VARCHAR );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(ZoneId value) {
return value == null ? null : value.getId();

View File

@ -34,6 +34,11 @@ public class ZoneOffsetJavaType extends AbstractClassJavaType<ZoneOffset> {
super( ZoneOffset.class, ImmutableMutabilityPlan.instance(), ZoneOffsetComparator.INSTANCE );
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
public String toString(ZoneOffset value) {
return value.getId();
}

View File

@ -62,6 +62,11 @@ public class ZonedDateTimeJavaType extends AbstractTemporalJavaType<ZonedDateTim
return (TemporalJavaType<X>) this;
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(ZonedDateTime value) {
return ISO_ZONED_DATE_TIME.format( value );

View File

@ -34,6 +34,11 @@ public class JavaTypeBasicAdaptor<T> extends AbstractClassJavaType<T> {
);
}
@Override
public boolean useObjectEqualsHashCode() {
return true;
}
@Override
public String toString(T value) {
return value.toString();