HHH-10289 - CPU performance regression in StatefulPersistenceContext.addEntry()

This commit is contained in:
Ståle W. Pedersen 2015-11-11 20:52:57 +01:00 committed by Steve Ebersole
parent 1568f89426
commit da4593de1b
2 changed files with 168 additions and 71 deletions

View File

@ -42,7 +42,6 @@ import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryFactory;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.ManagedEntity;
@ -485,13 +484,38 @@ public class StatefulPersistenceContext implements PersistenceContext {
final boolean disableVersionIncrement) {
final EntityEntry e;
if( (entity instanceof ManagedEntity) && ((ManagedEntity) entity).$$_hibernate_getEntityEntry() != null && status == Status.READ_ONLY) {
e = ((ManagedEntity) entity).$$_hibernate_getEntityEntry();
e.setStatus( status );
/*
IMPORTANT!!!
The following instanceof checks and castings are intentional.
DO NOT REFACTOR to make calls through the EntityEntryFactory interface, which would result
in polymorphic call sites which will severely impact performance.
When a virtual method is called via an interface the JVM needs to resolve which concrete
implementation to call. This takes CPU cycles and is a performance penalty. It also prevents method
in-ling which further degrades performance. Casting to an implementation and making a direct method call
removes the virtual call, and allows the methods to be in-lined. In this critical code path, it has a very
large impact on performance to make virtual method calls.
*/
if (persister.getEntityEntryFactory() instanceof MutableEntityEntryFactory) {
//noinspection RedundantCast
e = ( (MutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry(
status,
loadedState,
rowId,
id,
version,
lockMode,
existsInDatabase,
persister,
disableVersionIncrement,
this
);
}
else {
final EntityEntryFactory entityEntryFactory = persister.getEntityEntryFactory();
e = entityEntryFactory.createEntityEntry(
//noinspection RedundantCast
e = ( (ImmutableEntityEntryFactory) persister.getEntityEntryFactory() ).createEntityEntry(
status,
loadedState,
rowId,
@ -506,12 +530,22 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
entityEntryContext.addEntityEntry( entity, e );
// entityEntries.put(entity, e);
setHasNonReadOnlyEnties( status );
return e;
}
public EntityEntry addReferenceEntry(
final Object entity,
final Status status) {
((ManagedEntity)entity).$$_hibernate_getEntityEntry().setStatus( status );
entityEntryContext.addEntityEntry( entity, ((ManagedEntity)entity).$$_hibernate_getEntityEntry() );
setHasNonReadOnlyEnties( status );
return ((ManagedEntity)entity).$$_hibernate_getEntityEntry();
}
@Override
public boolean containsCollection(PersistentCollection collection) {
return collectionEntries.containsKey( collection );

View File

@ -20,14 +20,17 @@ import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl;
import org.hibernate.cache.spi.entry.StandardCacheEntryImpl;
import org.hibernate.engine.internal.CacheHelper;
import org.hibernate.engine.internal.StatefulPersistenceContext;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.EventType;
@ -603,8 +606,22 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
}
CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure( ce, factory );
Object entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey );
final Object entity;
if(entry.isReferenceEntry()) {
if( event.getInstanceToLoad() != null ) {
throw new HibernateException(
String.format( "Attempt to load entity [%s] from cache using provided object instance, but cache " +
"is storing references: "+ event.getEntityId()));
}
else {
entity = convertCacheReferenceEntryToEntity( (ReferenceCacheEntryImpl) entry,
event.getEntityId(), persister, event.getSession(), entityKey );
}
}
else {
entity = convertCacheEntryToEntity( entry, event.getEntityId(), persister, event, entityKey );
}
if ( !persister.isInstance( entity ) ) {
throw new WrongClassException(
"loaded object was of wrong class " + entity.getClass(),
@ -612,10 +629,78 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
persister.getEntityName()
);
}
return entity;
}
private Object convertCacheReferenceEntryToEntity(
ReferenceCacheEntryImpl referenceCacheEntry,
Serializable entityId,
EntityPersister persister,
EventSource session,
EntityKey entityKey) {
final Object entity = referenceCacheEntry.getReference();
if ( entity == null ) {
throw new IllegalStateException(
"Reference cache entry contained null : " + entityId);
}
else {
makeEntityCircularReferenceSafe(referenceCacheEntry, entityId, session, entity, entityKey);
//PostLoad is needed for EJB3
EventListenerGroup<PostLoadEventListener> evenListenerGroup = getEvenListenerGroup(session);
if(!evenListenerGroup.isEmpty()) {
postLoad(session, evenListenerGroup.listeners(), entity, entityId, persister);
}
return entity;
}
}
private void postLoad(EventSource session, Iterable<PostLoadEventListener> listeners,
Object entity, Serializable entityId, EntityPersister persister) {
PostLoadEvent postLoadEvent = new PostLoadEvent(session)
.setEntity(entity)
.setId(entityId)
.setPersister(persister);
for (PostLoadEventListener listener : listeners) {
listener.onPostLoad(postLoadEvent);
}
}
private void makeEntityCircularReferenceSafe(ReferenceCacheEntryImpl referenceCacheEntry,
Serializable entityId,
EventSource session,
Object entity,
EntityKey entityKey) {
final EntityPersister subclassPersister = referenceCacheEntry.getSubclassPersister();
// make it circular-reference safe
final StatefulPersistenceContext statefulPersistenceContext = (StatefulPersistenceContext) session.getPersistenceContext();
if ( (entity instanceof ManagedEntity) ) {
statefulPersistenceContext.addReferenceEntry(
entity,
Status.READ_ONLY
);
}
else {
TwoPhaseLoad.addUninitializedCachedEntity(
entityKey,
entity,
subclassPersister,
LockMode.NONE,
referenceCacheEntry.areLazyPropertiesUnfetched(),
referenceCacheEntry.getVersion(),
session
);
}
subclassPersister.afterInitialize( entity, referenceCacheEntry.areLazyPropertiesUnfetched(), session );
statefulPersistenceContext.initializeNonLazyCollections();
}
private Object convertCacheEntryToEntity(
CacheEntry entry,
Serializable entityId,
@ -636,38 +721,12 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
}
final Object entity;
if ( entry.isReferenceEntry() ) {
final Object optionalObject = event.getInstanceToLoad();
if ( optionalObject != null ) {
throw new HibernateException(
String.format(
"Attempt to load entity [%s] from cache using provided object instance, but cache " +
"is storing references",
MessageHelper.infoString( persister, entityId, factory )
)
);
}
ReferenceCacheEntryImpl referenceCacheEntry = (ReferenceCacheEntryImpl) entry;
entity = referenceCacheEntry.getReference();
if ( entity == null ) {
throw new IllegalStateException(
"Reference cache entry contained null : " + MessageHelper.infoString(
persister,
entityId,
factory
)
);
}
subclassPersister = referenceCacheEntry.getSubclassPersister();
}
else {
subclassPersister = factory.getEntityPersister( entry.getSubclass() );
final Object optionalObject = event.getInstanceToLoad();
entity = optionalObject == null
? session.instantiate( subclassPersister, entityId )
: optionalObject;
}
subclassPersister = factory.getEntityPersister( entry.getSubclass() );
final Object optionalObject = event.getInstanceToLoad();
entity = optionalObject == null
? session.instantiate( subclassPersister, entityId )
: optionalObject;
// make it circular-reference safe
TwoPhaseLoad.addUninitializedCachedEntity(
@ -683,38 +742,32 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
final Object[] values;
final Object version;
final boolean isReadOnly;
if ( entry.isReferenceEntry() ) {
values = null;
version = null;
isReadOnly = true;
final Type[] types = subclassPersister.getPropertyTypes();
// initializes the entity by (desired) side-effect
values = ( (StandardCacheEntryImpl) entry ).assemble(
entity, entityId, subclassPersister, session.getInterceptor(), session
);
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
TypeHelper.deepCopy(
values,
types,
subclassPersister.getPropertyUpdateability(),
values,
session
);
}
version = Versioning.getVersion( values, subclassPersister );
LOG.tracef( "Cached Version : %s", version );
final Object proxy = persistenceContext.getProxy( entityKey );
if ( proxy != null ) {
// there is already a proxy for this impl
// only set the status to read-only if the proxy is read-only
isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly();
}
else {
final Type[] types = subclassPersister.getPropertyTypes();
// initializes the entity by (desired) side-effect
values = ( (StandardCacheEntryImpl) entry ).assemble(
entity, entityId, subclassPersister, session.getInterceptor(), session
);
if ( ( (StandardCacheEntryImpl) entry ).isDeepCopyNeeded() ) {
TypeHelper.deepCopy(
values,
types,
subclassPersister.getPropertyUpdateability(),
values,
session
);
}
version = Versioning.getVersion( values, subclassPersister );
LOG.tracef( "Cached Version : %s", version );
final Object proxy = persistenceContext.getProxy( entityKey );
if ( proxy != null ) {
// there is already a proxy for this impl
// only set the status to read-only if the proxy is read-only
isReadOnly = ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isReadOnly();
}
else {
isReadOnly = session.isDefaultReadOnly();
}
isReadOnly = session.isDefaultReadOnly();
}
persistenceContext.addEntry(
@ -848,4 +901,14 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
.getEventListenerGroup( EventType.POST_LOAD )
.listeners();
}
private EventListenerGroup<PostLoadEventListener> getEvenListenerGroup(EventSource session) {
return session
.getFactory()
.getServiceRegistry()
.getService( EventListenerRegistry.class)
.getEventListenerGroup( EventType.POST_LOAD);
}
}