While using the YourKit performance monitoring tool I found two hotspots in file hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java function register(Statement statement, boolean cancelable) lines 67-70 at the first and last lines (67 and 70) inside Object.hashCode():
if ( xref.containsKey( statement ) ) {
throw new HibernateException( "JDBC Statement already registered" );
}
xref.put( statement, null );
There are two problems here: a) on the no-exception-thrown code path both the containsKey( statement ) and .put( statement, null ) hash the Statement object – hashing it twice is wasteful b) the Statement object (which in our particular case is a com.zaxxer.hikari.pool.HikariProxyPreparedStatement wrapping a com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement) is large and has a lot of fields, some of which seem to be to do with current status rather than identity, which could cause cache misses in the xref hash. If possible, it would be much more efficient to override the default behavior of .hashCode() and .equals() for implementations of Statement to only take into account fields that describe identity, rather than current status. In particular, if an implementation of Statement already has a functional unique identifier, both .hashCode() and .equals() should depend just on that. In our particular case, statement.connection.traceId and statement.delegate.traceId look like they might be unique identifiers. I understand that handling this correctly might require work from the HikariCP connection pool and/or the Microsoft JDBC driver code as well as or instead of from Hibernate. Issue a) can be fixed by changing the code lines above to:
Set<ResultSet> already = xref.put( statement, Collections.EMPTY_SET );
if ( already != null ) {
xref.put( statement, already );
throw new HibernateException( "JDBC Statement already registered" );
}
thus avoiding hashing twice on the no-exception-thrown code path, and also changing hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java function release(Statement statement) lines 81-92 from:
if ( log.isDebugEnabled() && !xref.containsKey( statement ) ) {
log.unregisteredStatement();
}
else {
final Set<ResultSet> resultSets = xref.get( statement );
if ( resultSets != null ) {
closeAll( resultSets );
}
xref.remove( statement );
}
to:
final Set<ResultSet> resultSets = xref.get( statement );
if ( resultSets != null ) {
closeAll( resultSets );
} else if ( log.isDebugEnabled() ) {
log.unregisteredStatement();
}
xref.remove( statement );
and changing function register(ResultSet resultSet, Statement statement) lines 203-213 from:
if ( statement != null ) {
if ( log.isDebugEnabled() && !xref.containsKey( statement ) ) {
log.debug( "ResultSet statement was not registered (on register)" );
}
Set<ResultSet> resultSets = xref.get( statement );
if ( resultSets == null ) {
resultSets = new HashSet<ResultSet>();
xref.put( statement, resultSets );
}
to:
if ( statement != null ) {
Set<ResultSet> resultSets = xref.get( statement );
if ( resultSets == null ) {
if ( log.isDebugEnabled() ) {
log.debug( "ResultSet statement was not registered (on register)" );
}
resultSets = Collections.EMPTY_SET;
}
if ( resultSets == Collections.EMPTY_SET ) {
resultSets = new HashSet<ResultSet>();
xref.put( statement, resultSets );
}
|