Project

General

Profile

Feature #6824

lazy initialization of collection class members

Added by Constantin Asofiei almost 4 years ago. Updated over 3 years ago.

Status:
WIP
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

80%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
reviewer:
production:
No
env_name:
topics:

History

#1 Updated by Constantin Asofiei almost 4 years ago

For example, there are BlockDefinition collection fields which can be assigned only when that field is required. Some of BlockDefinition fields will be fixed in 3821c/6129a, what is remaining is BlockDefinition.finalizables.

This task is meant to analyze what collection fields in high-impact FWD state (with lots of allocated instances) can be delayed initialization. Same with scope support: push a Collections.empty instead of a real instance, and create one only as needed.

The goal is to reduce the number of collection instances being created.

#2 Updated by Constantin Asofiei almost 4 years ago

Some changes are in 3821c/14272. Greg, please review.

#3 Updated by Constantin Asofiei over 3 years ago

Some notes of allocated java.util collections for a large customer application.
  • ArrayList - 1.6 million
    • ErrorManager$ErrorStatus.<init> 270k
    • AnnotatedAst.getImmediateChild 100k
    • DataSet.<init> 80k
    • SourceNameMapper 30k (from String.split)
    • HQLExpression.<init> 30k
    • ObjectOps.getExecuteMethod 30k (from String.format)
    • AbstractPArameter$Scope.<init> 26k
    • PreselectQuery.<init> 20k
    • DataSet.setBuffers 20k
    • PreselectQuery.execute 18k
  • HashMap - 1.4 million
    • HashSet.<init> 400k (from WidgetPool.scopeFinished, Session.invalidateRecords, AbstractTempTable.<init>, TemporaryBuffer.getAllSlaveBuffers, DataSet.<init>)
    • BufferImpl.<init> 200k
    • LinkedHashMap.<init> 180k (from RepositionCache$Node.<init>, orm.Persister.update)
    • RecordBuffer.<init> 180k
    • TriggerTracker.areAssignTriggersEnabled 84k
    • DataSet.<init> 80k
    • RecordBuffer.reportChange 52k
    • TemporaryBuffer$ReferenceProxy.<init> 44k
  • IdentityHashMap - 900k
    • TransactionManager.addFinalizable 123k
    • BufferManager.scopeStart 115k
    • ObjectOps$WorkArea.scopeFinished/scopeStart 110k
    • ArrayAssigner.scopeStart 76k
    • SavePointManager.<init> 76k
    • ObjectOps.pendingAssign 74k
    • DataSet.<init> 20k
    • ProcedureManager$WorkArea.searchInStack 31k
    • AbstractParameter$Scope.<init> 26k
    • BufferManager$PersistentProcScope.<init> 20k
  • BitSet - 900k
    • BaseRecord.getDirtyIndices 220k
    • RecordBuffer.<init> 180k
    • BaseRecord.getUnvalidatedIndices 76k
    • RecordBuffer.getIndexedProperties 56k
  • HashSet - 400k
    • WidgetPool$WorkArea.scopeFinished 90k
    • Session.invalidateRecords 50k
    • AbstractTempTable.<init> 40k
    • TemporaryBuffer.getAllSlaveBuffers 40k
    • RecordNursery.indexUpdated 21k
    • DataSet.<init> 40k
  • LinkedList - 115k
    • SourceNameMapper.removeUpPath 21k
    • SourceNameMapper.getConstructors 12k
    • SourceNameMapper.canonicalize 10k
    • ProcedureManager.publish 10k
  • ArrayDeque - 120k
    • WidgetPool$WorkArea.scopeStart 86k
    • ArrayAssigner$WorkArea.scopeStart 13k
    • ProcedureManager$ProcedureData.<init> 13k
    • ObjectResource.<init> 12k
  • LinkedHashMap - 320k
    • AnnotatedAst.initialize 116k (from AnnotatedAst.duplicateImpl)
    • @RepositionCache$Node.<init> 81k
    • Persister.update 35k
    • AnnotatedAst.putAnnotationImpl 26k
  • WeakHashMap - 53k
    • TemporaryBuffer.define 40k
    • ProcedureManager$ProcedureData.<init> 13k
The point here is to investigate each case and:
  • reduce the number of allocations, by lazy initialization (don't create it until is really needed).
  • in some cases (like stacks/deques) it may be possible to use a single stack and keep together all the info in a single entry in the stack.

#4 Updated by Constantin Asofiei over 3 years ago

Some notes :
  • BufferImpl.callbacks can be lazily created, not all buffers are used in FILL operations.
  • RecordBuffer - getterDatums and setterDatums can be lazily created, as these are used only via getGetterDatums and getSetterDatums, and are needed only in specific usage of a RecordBuffer.
  • BaseRecord.getDirtyIndices(BitSet[] indices, int dirtyOffset, BitSet filter, boolean full) can easily return null if there are no 'dirty' bits set - and the callers can handle this. I'm not sure about the filter - if all bits are set, we can just pass a null instance to it, instead of creating a new BitSet with all bits set.

#5 Updated by Constantin Asofiei over 3 years ago

  • Status changed from New to WIP
  • Assignee set to Constantin Asofiei

#6 Updated by Constantin Asofiei over 3 years ago

A batch of changes (including #6824-4) are in 6129b/14348

Please review.

#7 Updated by Ovidiu Maxiniuc over 3 years ago

Review of: 6129b/14348.

I am OK with the code. However, in DataSet, when it is decided that callbacks == null there is no point in calling invokeCallback. That would require for calls of other methods like asHandle() to set the parameters. Example DataSet:1928 line:

      if (!invokeCallback(asHandle(), callbacks == null ? null : callbacks.get(BEFORE_FILL_EVENT)))
could be rewritten as:
      if (callbacks != null && !invokeCallback(asHandle(), callbacks.get(BEFORE_FILL_EVENT)))
Other occurrences at lines: 1963, 2192, 2213, 2239.


I also noticed the replacement of ArrayList usages with HashSet. I think we can do even better, using BitSet instead. I created a quick test in which N = 1000000 objects of each type were created and used. The result are:

Obj Type Creation Add elements Remove elements
Time Add memory Time Add memory Time Add memory
BitSet 54 +420 403 0 432 0
HashSet 36 +65 6254 +17983 4045 +20896

While the HashSet performs slightly better when created, the add and remove operations are 10-15X faster with BitSet s and do not require additional memory.

#8 Updated by Constantin Asofiei over 3 years ago

Some other changes related to this are in 6129b/14366.

#9 Updated by Constantin Asofiei over 3 years ago

  • % Done changed from 0 to 80

The fixes part of the fixes from previous notes were determined from profiling and manual/automated code review, used to identify the collections for which their initialization can be delayed. There are still a large number of allocated collections from H2 and other FWD parts which can't be refactored easily (like scope processing).

Also available in: Atom PDF