Skip to content

Commit 55702e4

Browse files
beikovsebersole
authored andcommitted
HHH-18271 Avoid mega-morphic callsites for equals/hashCode with known types
1 parent 850a2a0 commit 55702e4

35 files changed

+197
-36
lines changed

hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionKey.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,23 @@
2626
public final class CollectionKey implements Serializable {
2727
private final String role;
2828
private final Object key;
29-
private final Type keyType;
29+
private final @Nullable Type keyType;
3030
private final SessionFactoryImplementor factory;
3131
private final int hashCode;
3232

3333
public CollectionKey(CollectionPersister persister, Object key) {
3434
this(
3535
persister.getRole(),
3636
key,
37-
persister.getKeyType(),
37+
persister.getKeyType().getTypeForEqualsHashCode(),
3838
persister.getFactory()
3939
);
4040
}
4141

4242
private CollectionKey(
4343
String role,
4444
@Nullable Object key,
45-
Type keyType,
45+
@Nullable Type keyType,
4646
SessionFactoryImplementor factory) {
4747
this.role = role;
4848
if ( key == null ) {
@@ -58,7 +58,7 @@ private CollectionKey(
5858
private int generateHashCode() {
5959
int result = 17;
6060
result = 37 * result + role.hashCode();
61-
result = 37 * result + keyType.getHashCode( key, factory );
61+
result = 37 * result + ( keyType == null ? key.hashCode() : keyType.getHashCode( key, factory ) );
6262
return result;
6363
}
6464

@@ -90,7 +90,7 @@ public boolean equals(final @Nullable Object other) {
9090
final CollectionKey that = (CollectionKey) other;
9191
return that.role.equals( role )
9292
&& ( this.key == that.key ||
93-
keyType.isEqual( this.key, that.key, factory ) );
93+
keyType == null ? this.key.equals( that.key ) : keyType.isEqual( this.key, that.key, factory ) );
9494
}
9595

9696
@Override

hibernate-core/src/main/java/org/hibernate/engine/spi/EntityKey.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.AssertionFailure;
1515
import org.hibernate.persister.entity.EntityPersister;
1616
import org.hibernate.pretty.MessageHelper;
17+
import org.hibernate.type.Type;
1718

1819
import org.checkerframework.checker.nullness.qual.Nullable;
1920

@@ -59,7 +60,8 @@ private int generateHashCode() {
5960
int result = 17;
6061
final String rootEntityName = persister.getRootEntityName();
6162
result = 37 * result + rootEntityName.hashCode();
62-
result = 37 * result + persister.getIdentifierType().getHashCode( identifier, persister.getFactory() );
63+
final Type identifierType = persister.getIdentifierType().getTypeForEqualsHashCode();
64+
result = 37 * result + ( identifierType == null ? identifier.hashCode() : identifierType.getHashCode( identifier, persister.getFactory() ) );
6365
return result;
6466
}
6567

@@ -99,8 +101,10 @@ public boolean equals(@Nullable Object other) {
99101
}
100102

101103
private boolean sameIdentifier(final EntityKey otherKey) {
102-
return this.identifier == otherKey.identifier ||
103-
persister.getIdentifierType().isEqual( otherKey.identifier, this.identifier, persister.getFactory() );
104+
final Type identifierType;
105+
return this.identifier == otherKey.identifier || (
106+
(identifierType = persister.getIdentifierType().getTypeForEqualsHashCode()) == null && identifier.equals( otherKey.identifier )
107+
|| identifierType != null && identifierType.isEqual( otherKey.identifier, this.identifier, persister.getFactory() ) );
104108
}
105109

106110
private boolean samePersistentType(final EntityKey otherKey) {

hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@
2727
import org.hibernate.type.descriptor.ValueBinder;
2828
import org.hibernate.type.descriptor.ValueExtractor;
2929
import org.hibernate.type.descriptor.WrapperOptions;
30-
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
3130
import org.hibernate.type.descriptor.java.JavaType;
3231
import org.hibernate.type.descriptor.java.MutabilityPlan;
3332
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
3433
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
3534
import org.hibernate.type.descriptor.jdbc.JdbcType;
3635

36+
import org.checkerframework.checker.nullness.qual.Nullable;
37+
3738
/**
3839
* Convenience base class for {@link BasicType} implementations.
3940
* Packages a {@link JavaType} with a {@link JdbcType}.
@@ -50,7 +51,7 @@ public abstract class AbstractStandardBasicType<T>
5051
private final ValueBinder<T> jdbcValueBinder;
5152
private final ValueExtractor<T> jdbcValueExtractor;
5253
private final JdbcLiteralFormatter<T> jdbcLiteralFormatter;
53-
private final AbstractClassJavaType<T> javaTypeAsAbstractClassJavaType;
54+
private final @Nullable Type typeForEqualsHashCode;
5455
private final Class javaTypeClass;
5556
private final MutabilityPlan<T> mutabilityPlan;
5657
private final Comparator<T> javatypeComparator;
@@ -68,13 +69,7 @@ public AbstractStandardBasicType(JdbcType jdbcType, JavaType<T> javaType) {
6869
this.javaTypeClass = javaType.getJavaTypeClass();
6970
this.mutabilityPlan = javaType.getMutabilityPlan();
7071
this.javatypeComparator = javaType.getComparator();
71-
//This is a dispatch optimisation to avoid megamorphic invocations on the most common type:
72-
if ( javaType instanceof AbstractClassJavaType ) {
73-
this.javaTypeAsAbstractClassJavaType = (AbstractClassJavaType) javaType;
74-
}
75-
else {
76-
this.javaTypeAsAbstractClassJavaType = null;
77-
}
72+
this.typeForEqualsHashCode = javaType.useObjectEqualsHashCode() ? null : this;
7873
}
7974

8075
@Override
@@ -98,13 +93,7 @@ public Class<T> getJavaType() {
9893
}
9994

10095
public T fromString(CharSequence string) {
101-
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
102-
if ( type != null ) {
103-
return type.fromString( string );
104-
}
105-
else {
106-
return javaType.fromString( string );
107-
}
96+
return javaType.fromString( string );
10897
}
10998

11099
protected MutabilityPlan<T> getMutabilityPlan() {
@@ -196,25 +185,19 @@ public boolean isEqual(Object one, Object another) {
196185
else if ( one == null || another == null ) {
197186
return false;
198187
}
188+
else if ( typeForEqualsHashCode == null ) {
189+
return one.equals( another );
190+
}
199191
else {
200-
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
201-
if ( type != null ) {
202-
//Optimize for the most common case: avoid the megamorphic call
203-
return type.areEqual( (T) one, (T) another );
204-
}
205-
else {
206-
return javaType.areEqual( (T) one, (T) another );
207-
}
192+
return javaType.areEqual( (T) one, (T) another );
208193
}
209194
}
210195

211196
@Override
212197
@SuppressWarnings("unchecked")
213198
public int getHashCode(Object x) {
214-
final AbstractClassJavaType<T> type = this.javaTypeAsAbstractClassJavaType;
215-
if ( type != null ) {
216-
//Optimize for the most common case: avoid the megamorphic call
217-
return type.extractHashCode( (T) x );
199+
if ( typeForEqualsHashCode == null ) {
200+
return x.hashCode();
218201
}
219202
else {
220203
return javaType.extractHashCode( (T) x );
@@ -226,6 +209,11 @@ public final int getHashCode(Object x, SessionFactoryImplementor factory) {
226209
return getHashCode( x );
227210
}
228211

212+
@Override
213+
public @Nullable Type getTypeForEqualsHashCode() {
214+
return typeForEqualsHashCode;
215+
}
216+
229217
@Override
230218
@SuppressWarnings("unchecked")
231219
public final int compare(Object x, Object y) {

hibernate-core/src/main/java/org/hibernate/type/Type.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,15 @@ default String getReturnedClassName() {
219219
*/
220220
int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException;
221221

222+
/**
223+
* The type to use for {@code equals()} and {@code hashCode()} computation.
224+
* When {@code null}, use {@link Object#equals(Object)} and {@link Object#hashCode()}.
225+
* This is useful to avoid mega-morphic callsites.
226+
*/
227+
default @Nullable Type getTypeForEqualsHashCode() {
228+
return this;
229+
}
230+
222231
/**
223232
* Perform a {@link java.util.Comparator}-style comparison of the given values.
224233
*

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public BooleanJavaType(char characterValueTrue, char characterValueFalse) {
4747
stringValueFalse = String.valueOf( characterValueFalse );
4848
}
4949

50+
@Override
51+
public boolean useObjectEqualsHashCode() {
52+
return true;
53+
}
54+
5055
@Override
5156
public String toString(Boolean value) {
5257
return value == null ? null : value.toString();

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public ByteJavaType() {
3131
super( Byte.class );
3232
}
3333

34+
@Override
35+
public boolean useObjectEqualsHashCode() {
36+
return true;
37+
}
38+
3439
@Override
3540
public String toString(Byte value) {
3641
return value == null ? null : value.toString();

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public CharacterJavaType() {
2525
super( Character.class );
2626
}
2727

28+
@Override
29+
public boolean useObjectEqualsHashCode() {
30+
return true;
31+
}
32+
2833
@Override
2934
public String toString(Character value) {
3035
return value.toString();

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public ClassJavaType() {
2222
super( Class.class );
2323
}
2424

25+
@Override
26+
public boolean useObjectEqualsHashCode() {
27+
return true;
28+
}
29+
2530
public String toString(Class value) {
2631
return value.getName();
2732
}

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public CurrencyJavaType() {
2424
super( Currency.class );
2525
}
2626

27+
@Override
28+
public boolean useObjectEqualsHashCode() {
29+
return true;
30+
}
31+
2732
@Override
2833
public String toString(Currency value) {
2934
return value.getCurrencyCode();

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
3535
return DoubleJdbcType.INSTANCE;
3636
}
3737

38+
@Override
39+
public boolean useObjectEqualsHashCode() {
40+
return true;
41+
}
42+
3843
@Override
3944
public String toString(Double value) {
4045
return value == null ? null : value.toString();

0 commit comments

Comments
 (0)