Update MethodDictionary rebuild to speed up trait recompilation#19870
Update MethodDictionary rebuild to speed up trait recompilation#19870jecisc wants to merge 41 commits into
Conversation
…e way to reject some selectors
For now one is failing
Gabriel-Darbord
left a comment
There was a problem hiding this comment.
Thanks for this PR, this is great for Moose where traits are omnipresent :)
I have a few technical and quality suggestions.
I can't really comment on semantics and validity, but I don't see any blatant issue there.
| | innerInfos aliassed | | ||
| "I calculate the new aliases selectors" | ||
| innerInfos := inner methodsCompilationInfo. | ||
| aliassed := OrderedCollection new: self aliases size. | ||
|
|
||
| self aliases associations do: [ :association | | ||
| innerInfos | ||
| detect: [ :info | info selector = association value ] | ||
| ifFound: [ :info | | ||
| aliassed add: (TraitMethodCompilationInfo new | ||
| selector: association key; | ||
| definingTrait: info definingTrait; | ||
| compiledMethod: info compiledMethod; | ||
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: association key); | ||
| yourself) ] ]. | ||
|
|
||
| ^ innerInfos , aliassed |
There was a problem hiding this comment.
aliassed -> aliased
| | innerInfos aliassed | | |
| "I calculate the new aliases selectors" | |
| innerInfos := inner methodsCompilationInfo. | |
| aliassed := OrderedCollection new: self aliases size. | |
| self aliases associations do: [ :association | | |
| innerInfos | |
| detect: [ :info | info selector = association value ] | |
| ifFound: [ :info | | |
| aliassed add: (TraitMethodCompilationInfo new | |
| selector: association key; | |
| definingTrait: info definingTrait; | |
| compiledMethod: info compiledMethod; | |
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: association key); | |
| yourself) ] ]. | |
| ^ innerInfos , aliassed | |
| | innerInfos aliased | | |
| "I calculate the new aliases selectors" | |
| innerInfos := inner methodsCompilationInfo. | |
| aliased := OrderedCollection new: self aliases size. | |
| self aliases associations do: [ :association | | |
| innerInfos | |
| detect: [ :info | info selector = association value ] | |
| ifFound: [ :info | | |
| aliased add: (TraitMethodCompilationInfo new | |
| selector: association key; | |
| definingTrait: info definingTrait; | |
| compiledMethod: info compiledMethod; | |
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: association key); | |
| yourself) ] ]. | |
| ^ innerInfos , aliased |
| innerInfos := inner methodsCompilationInfo. | ||
| aliassed := OrderedCollection new: self aliases size. | ||
|
|
||
| self aliases associations do: [ :association | |
There was a problem hiding this comment.
| self aliases associations do: [ :association | | |
| self aliases associationsDo: [ :association | |
| TraitedClass allSlots) anySatisfy: [ :x | x name = e name ] ] | ||
|
|
||
| | slotNamesToReject | | ||
| "Save in a variable to not recomcatenate for each slot" |
There was a problem hiding this comment.
| "Save in a variable to not recomcatenate for each slot" | |
| "Save in a variable to not re-concatenate for each slot" |
| | innerInfos traitInfos localSelectors methods | | ||
| "We first collect the compilation info for the local methods I have." | ||
| innerInfos := innerClass localMethods collect: [ :method | | ||
| TraitMethodCompilationInfo new | ||
| selector: method selector; | ||
| definingTrait: self; | ||
| compiledMethod: method; | ||
| sourceCode: method sourceCode; | ||
| yourself ]. | ||
|
|
||
| "If I need to alias the talent initializer, I do it now." | ||
| innerInfos do: [ :info | | ||
| info selector = #initializeTalent ifTrue: [ | ||
| info | ||
| selector: self initializeSelectorForMe; | ||
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: self initializeSelectorForMe) ] ]. | ||
|
|
There was a problem hiding this comment.
Memoize self initializeSelectorForMe since it's re-computed each time by TaCompositionElement?
| | innerInfos traitInfos localSelectors methods | | |
| "We first collect the compilation info for the local methods I have." | |
| innerInfos := innerClass localMethods collect: [ :method | | |
| TraitMethodCompilationInfo new | |
| selector: method selector; | |
| definingTrait: self; | |
| compiledMethod: method; | |
| sourceCode: method sourceCode; | |
| yourself ]. | |
| "If I need to alias the talent initializer, I do it now." | |
| innerInfos do: [ :info | | |
| info selector = #initializeTalent ifTrue: [ | |
| info | |
| selector: self initializeSelectorForMe; | |
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: self initializeSelectorForMe) ] ]. | |
| | innerInfos selector traitInfos localSelectors methods | | |
| "We first collect the compilation info for the local methods I have." | |
| innerInfos := innerClass localMethods collect: [ :method | | |
| TraitMethodCompilationInfo new | |
| selector: method selector; | |
| definingTrait: self; | |
| compiledMethod: method; | |
| sourceCode: method sourceCode; | |
| yourself ]. | |
| "If I need to alias the talent initializer, I do it now." | |
| selector := self initializeSelectorForMe. | |
| innerInfos do: [ :info | | |
| info selector = #initializeTalent ifTrue: [ | |
| info | |
| selector: selector; | |
| sourceCode: (info compiledMethod getSourceReplacingSelectorWith: selector) ] ]. |
| | preferedInfos | | ||
| preferedInfos := self preferedTrait methodsCompilationInfo. | ||
|
|
||
| implementations | ||
| detect: [ :info | preferedInfos includes: info ] |
There was a problem hiding this comment.
prefered -> preferred, but changing preferedTrait is outside the current scope of this PR.
| | preferedInfos | | |
| preferedInfos := self preferedTrait methodsCompilationInfo. | |
| implementations | |
| detect: [ :info | preferedInfos includes: info ] | |
| | preferredInfos | | |
| preferredInfos := self preferedTrait methodsCompilationInfo. | |
| implementations | |
| detect: [ :info | preferredInfos includes: info ] |
| | selectors removedSelectors modified | | ||
| | compilationInfos removedSelectors modified | | ||
| "During the creation of the class or after a change in the traitComposition, the whole method dictionary is calculated. | ||
| If I return true, my users should be updated""1. I recreate the local methodDict" |
There was a problem hiding this comment.
Consider formatting this method?
Also these comments have been unintentionally merged previously:
| If I return true, my users should be updated""1. I recreate the local methodDict" | |
| If I return true, my users should be updated" | |
| "1. I recreate the local methodDict" |
| removedSelectors := self methodDict keys reject: [ :aSelector | | ||
| (compilationInfos anySatisfy: [ :compilationInfo | compilationInfo selector = aSelector ]) or: [ self isSelectorToKeep: aSelector ] ]. | ||
| modified := modified | removedSelectors isNotEmpty. | ||
| removedSelectors do: [ :aSelector | | ||
| self methodDict removeKey: aSelector. | ||
| self removeFromProtocols: aSelector ]. | ||
| self methodDict removeKey: aSelector. | ||
| self removeFromProtocols: aSelector ]. |
There was a problem hiding this comment.
removedSelectors seems like an unnecessary intermediate collection:
self methodDict keys do: [ :aSelector |
((compilationInfos anySatisfy: [ :compilationInfo | compilationInfo selector = aSelector ]) or: [ self isSelectorToKeep: aSelector ]) ifTrue: [
modified := true.
self methodDict removeKey: aSelector.
self removeFromProtocols: aSelector ] ].Explicitly use keys do: instead of keysDo: to avoid modification during iteration.
You could also try swapping conditions to check isSelectorToKeep: first to be potentially faster.
| self == anObject ifTrue: [ ^ true ]. | ||
| self class = anObject class ifFalse: [ ^ false ]. | ||
| ^ selector = anObject selector and: [ | ||
| definingTrait = anObject definingTrait and: [ sourceCode = anObject sourceCode and: [ self protocol = anObject protocol ] ] ] |
There was a problem hiding this comment.
Protocol comparison is faster than sourceCode comparison and we want to fail-fast:
| definingTrait = anObject definingTrait and: [ sourceCode = anObject sourceCode and: [ self protocol = anObject protocol ] ] ] | |
| definingTrait = anObject definingTrait and: [ self protocol = anObject protocol and: [ sourceCode = anObject sourceCode ] ] ] |
| super printOn: aStream. | ||
|
|
||
| aStream | ||
| nextPutAll: ' [ '; |
There was a problem hiding this comment.
More in-line with other printOn implementation, do not include a space after the bracket, though the space before is up to you (personally I prefer it compact).
| nextPutAll: ' [ '; | |
| nextPutAll: '['; |
| nextPutAll: '>>'; | ||
| print: selector. | ||
|
|
||
| self compiledMethod ifNotNil: [ :cm | cm isRequired ifTrue: [ aStream nextPutAll: ' - Explicit requirement' ] ]. |
There was a problem hiding this comment.
If memoized and not afraid of concurrency (compiledMethod could change between nil check and block execution, but I don't think that's a concern for this data structure): cm isRequired -> self isExplicitRequirement
This PR brings multiple speed up for large trait recompilation.
Recompiling large traits can be really slow because it needs to rebuild the method dictionary of each of the trait user.
This was done by iterating over the selectors of the trait composition and installing a copy of the method associated in the user. But we traversed multiple time the trait composition by doing this.
Now I build a compilation info object containing all the info directly preventing multiple visits of the trait composition.
Most of the time we still spend in the rebuild is spend in recompiling the methods if the user has a slot. The installation time of the method is much shorter.
For comparison, in a vanilla Moose image it was taking 72sec to add a slot to TEntityMetaLevelDependency. With this change it drops to 27sec.
There might be ways to speed up a little more operations, I might explore this later.