Feature #6824
lazy initialization of collection class members
80%
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
java.util collections for a large customer application.
ArrayList- 1.6 millionErrorManager$ErrorStatus.<init>270kAnnotatedAst.getImmediateChild100kDataSet.<init>80kSourceNameMapper30k (from String.split)HQLExpression.<init>30kObjectOps.getExecuteMethod30k (from String.format)AbstractPArameter$Scope.<init>26kPreselectQuery.<init>20kDataSet.setBuffers20kPreselectQuery.execute18k
HashMap- 1.4 millionHashSet.<init>400k (from WidgetPool.scopeFinished, Session.invalidateRecords, AbstractTempTable.<init>, TemporaryBuffer.getAllSlaveBuffers, DataSet.<init>)BufferImpl.<init>200kLinkedHashMap.<init>180k (from RepositionCache$Node.<init>, orm.Persister.update)RecordBuffer.<init>180kTriggerTracker.areAssignTriggersEnabled84kDataSet.<init>80kRecordBuffer.reportChange52kTemporaryBuffer$ReferenceProxy.<init>44k
IdentityHashMap- 900kTransactionManager.addFinalizable123kBufferManager.scopeStart115kObjectOps$WorkArea.scopeFinished/scopeStart110kArrayAssigner.scopeStart76kSavePointManager.<init>76kObjectOps.pendingAssign74kDataSet.<init>20kProcedureManager$WorkArea.searchInStack31kAbstractParameter$Scope.<init>26kBufferManager$PersistentProcScope.<init>20k
BitSet- 900kBaseRecord.getDirtyIndices220kRecordBuffer.<init>180kBaseRecord.getUnvalidatedIndices76kRecordBuffer.getIndexedProperties56k
HashSet- 400kWidgetPool$WorkArea.scopeFinished90kSession.invalidateRecords50kAbstractTempTable.<init>40kTemporaryBuffer.getAllSlaveBuffers40kRecordNursery.indexUpdated21kDataSet.<init>40k
LinkedList- 115kSourceNameMapper.removeUpPath21kSourceNameMapper.getConstructors12kSourceNameMapper.canonicalize10kProcedureManager.publish10k
ArrayDeque- 120kWidgetPool$WorkArea.scopeStart86kArrayAssigner$WorkArea.scopeStart13kProcedureManager$ProcedureData.<init>13kObjectResource.<init>12k
LinkedHashMap- 320kAnnotatedAst.initialize116k (from AnnotatedAst.duplicateImpl)- @RepositionCache$Node.<init> 81k
Persister.update35kAnnotatedAst.putAnnotationImpl26k
WeakHashMap- 53kTemporaryBuffer.define40kProcedureManager$ProcedureData.<init>13k
- 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
BufferImpl.callbackscan be lazily created, not all buffers are used in FILL operations.RecordBuffer-getterDatumsandsetterDatumscan be lazily created, as these are used only viagetGetterDatumsandgetSetterDatums, 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).