-
Notifications
You must be signed in to change notification settings - Fork 397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Simplify lowest level compiler allocation classes #3396
Conversation
3c5659b
to
2174da2
Compare
genie-omr build all |
2174da2
to
60b0563
Compare
genie-omr build all |
2768da9
to
224c7ae
Compare
genie-omr build all |
224c7ae
to
bc092a3
Compare
genie-omr build all |
bc092a3
to
b8e4fb5
Compare
genie-omr build arm |
genie-omr build all |
Note to reviewers; I still have to update the compiler memory documentation in this PR. |
compiler/control/CompileMethod.cpp
Outdated
debugSegmentAllocator : defaultSegmentAllocator; | ||
#endif | ||
|
||
TR::Region dispatchRegion(segmentAllocator, rawAllocator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
segmentAllocator
isn't defined when using OLD_MEMORY
; I guess the variable is named segmentProvider
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the change below (ie using segmentProvider
for OLD_MEMORY
, and segmentAllocator
for the new stuff), it seems like you're going to have to duplicate the initialization of the TR::Region
in each of the ifdef
sections
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, will fix
void *p = _rawAllocator.allocate(sizeToAllocate, hint); | ||
TR::MemorySegment *newSegment = new (_rawAllocator) TR::MemorySegment(p, sizeToAllocate); | ||
if (newSegment == NULL) | ||
throw std::bad_alloc(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless std::nothrow_t tag
is specified as a parameter to the placement new
(which in this case it isn't), OMR::RawAllocator
throws a std::bad_alloc()
, so this is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another good catch, will remove the check
break; | ||
} | ||
} | ||
TR_ASSERT(found, "Request to deallocate segment not found in allocated segment list"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How "fatal" is it if we can't find the segment in this deque?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At best it's going to be a memory leak, and at worst I suppose it's a sign that memory corruption is likely happening in the compilation. I suppose a TR_ASSERT_FATAL
would be a better option.
while (!_allocatedSegments.empty()) | ||
{ | ||
TR::MemorySegment &segment = _allocatedSegments.front().get(); | ||
deallocate(segment); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious as to whether BackingMemoryAllocator
can be extensible, and if so, whether we want to enforce that the OMR
version of this is guaranteed to get called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that's appropriate: if someone creates an extension of BackingMemoryAllocator that implements a different allocate()
function then presumably they should have the ability to also provide a different deallocate()
function that matches?
Someone who did that would probably want to further break down deallocate()
so they could reuse some parts of it without necessarily using other parts (exercise left for those who need it :) ?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah fair enough; I figured I'd mention it jic :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this a second time, wouldn't this result in the code going through the _allocatedSegments
list for every iteration of the loop (since deallocate
goes through the list to see if the segment is in the list)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, because they start at the same end of the list :). It asks what the head is, then removes the head (so deallocate
just picks off the first segment). It might be possible to refactor deallocate
to not rely on it and it probably deserves a comment, but i guess I went the easy way.
For the record, I found the list iteration in the earlier implementation, which I initially duplicated, was not as efficient as it should be, so I tweaked it a bit to make it better. So good catch :) .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh I see, sneaky :P
|
||
|
||
TR::MemorySegment & | ||
OMR::BackingMemoryAllocator::allocate(size_t requiredSize, void * hint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method doesn't check the _allocationLimit
even though that concept exists at the BackingMemoryAllocator
level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um, yeah...good point :)
What is the reason |
@@ -203,7 +208,8 @@ class TypeDictionary | |||
MemoryManager(); | |||
~MemoryManager(); | |||
|
|||
TR::SegmentProvider *_segmentProvider; | |||
TR::BackingMemoryAllocator *_backingMemoryAllocator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth having a comment here stating how this should never be used by anything but _segmentAllocator
.
jitbuilder/control/Jit.cpp
Outdated
@@ -120,12 +123,14 @@ initializeJitBuilder(TR_RuntimeHelper *helperIDs, void **helperAddresses, int32_ | |||
// Create a bootstrap raw allocator. | |||
// | |||
TR::RawAllocator rawAllocator; | |||
TR::BackingMemoryAllocator backingAllocator(rawAllocator, 1<< 20); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above. Although now that I think of it (and I'm not saying it should be done here), but since the backingAllocator
and segmentAllocator
seem to always go together at least during initialization, perhaps it's worth having them encapsulated in a struct so that the lifetimes of both are linked in places where it makes sense that they be linked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They don't have to be linked together. That's just how they're used currently....I don't believe there's a requirement for the BackingMemoryAllocator
to die just because the SegmentAllocator
dies...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe there's a requirement for the BackingMemoryAllocator to die just because the SegmentAllocator dies...
Yeah that's a good point; I guess I was just looking at the current uses of it, and it seems to have the same lifetime as the SegmentAllocator
. That said, there's no reason that in the future you couldn't initialize another SegmentAllocator
once the first one has been destroyed. Still, it's worth maybe putting some warning comments to alert people to the fact that this is a very subtle piece of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation lgtm (aside from the comments already made)
Btw, forgot to mention earlier, but the copyrights need to be updated :) |
Ah, now I understand the source of your question: the |
c1394f5
to
69880ab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
RawAllocator classes: | ||
+----------------------------+ +-----------------------------+ | ||
| | using | | | ||
| TR::RawAllocator +<--------+ OMR::RawAllocator | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the word using
a bit confusing here. Is it referring to the fact that OMR::RawAllocator
should be "used" via TR::RawAllocator
? Why not just show the inheritance relationship instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see a good way to represent it, so "confusing" is a perfectly apt word to use.
OMR::RawAllocator
is placed into the TR
namespace via a using
statement:
namespace TR { using OMR::RawAllocator };
69880ab
to
292ef4e
Compare
genie-omr build all |
292ef4e
to
db1c208
Compare
genie-omr build all |
db1c208
to
1d4edf3
Compare
genie-omr build all |
The lowest level memory allocation classes in the OMR compiler have a complicated hierarchy and relationship that many people have struggled to understand and maintain. The complexity came about trying to hold a unified picture of memory allocation between OMR (which just uses a raw malloc allocator) and OpenJ9 (which uses J9MemorySegments allocated from the VM a.k.a. "the system"). Unfortunately, this unity did not materialize and so these lower level allocation classes are not actually shared with OpenJ9. The set of changes in this commit is motivated by the effort to try to compile the basic JitBuilder files in compiler/ilgen as part of OpenJ9. In this commit, I brought over the code from OpenJ9's SystemSegmentProvider and refactored it so that it could operate in OMR using TR::MemorySegments rather than J9MemorySegments. I changed its name to SegmentAllocator and declare the SegmentAllocator as the base layer for the higher level allocation support for e.g. regions and STL and other objects in the compiler component. This commit is the first stage in a series of coordinated changes to OMR and OpenJ9 to migrate from the "old" memory allocation scheme to the "new" memor allocation scheme. In this change, *most* of the new code is implemented inside a new compiler/env/mem directory which contains any files from compiler/env that required substantial modification. This commit also introduces a temporary macro called, uninspiringly, OLD_MEMORY: if OLD_MEMORY is defined then the old OMR memory allocation classes will be built rather than the new ones. By default after this commit, OMR will build with the *new* memory allocation classes (e.g. into jitbuilder and compilertest). Most consumers of the OMR compiler should experience *zero* functional differences as the memory classes being changed are at the lowest levels of the compiler implementation (well below than the facilities most compiler developers use directly). The SegmentAllocator will hold a BackingMemoryAllocator which will itself hold a RawAllocator. In fact, the BackingMemoryAllocator in OMR is an only slightly beefed up RawAllocator which returns memory wrapped in a TR::MemorySegment. The SegmentPool code was removed because nobody was using it. he DebugSegmentProvider code has been moved into DebugRawAllocator (subclass of TR::RawAllocator) as well as DebugSegmentAllocator (subclass of TR::SegmentAllocator). I added some tracing output to the SegmentAllocator and BackingMemoryAllocator to track segments as they are allocated and deallocated (see MEMLOG macros in each file). This debug output uses printfs due to the primordial environment of the memory allocators, and is activated by environment variables OMRDebug_BackingMemory=1 or 2 or 3 (increasing detail) OMRDebug_SegmentAllocator=1 or 2 or 3 (increasing detail) While debugging the memory allocation layers, I discovered that the original list management for segments was less efficient than it could be, in that new segments were typically added at the end of lists but searched starting from the beginning. This commit changes the order of search to require fewer probes (less than half the probes required in some very unscientific studies with very simple compilations). I doubt it will have any performance impact in most workloads but it felt better to fix it. BackingMemoryAllocator, SegmentAllocator, and DebugSegmentAllocator are all extensible classes to facilitate a similar refactoring extravaganza to be inflicted upon OpenJ9 very soon. Signed-off-by: Mark Stoodley <[email protected]>
The compiler's lowest level memory allocation implementation is changing so provide documention for the new scheme. But since the old implementation is still available via the OLD_MEMORY macro, the older documentation is not removed, but moved to a file prefixed by "Old" and with comments at the top directing towards the new file. Once all downstream projects have stopped using the old scheme, this older documentation will be removed. Signed-off-by: Mark Stoodley <[email protected]>
Signed-off-by: Mark Stoodley <[email protected]>
1d4edf3
to
d1ce3e6
Compare
genie-omr build all |
fragmentation from the raw allocator. For example, the default | ||
method compilation requests will construct a backing memory allocator | ||
with a default segment size of 64KB (`1 << 16`), which means all | ||
allocations from the backing memory allocator will rounded up to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allocations from the backing memory allocator will rounded up to | |
allocations from the backing memory allocator will be rounded up to |
The backing memory allocator tracks how much memory has been allocated | ||
via `_bytesAllocated` which can be queried via `bytesAllocated()`. The | ||
backing memory allocator can also be directed to enforce an | ||
allocation limit via a constructor option which sets the `_allocationLimit` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say "constructor option", do you mean "constructor parameter"?
allocation limit via a constructor option which sets the `_allocationLimit` | |
allocation limit via a constructor parameter which sets the `_allocationLimit` |
required by the compiler). | ||
|
||
This reuse has one restriction to simplify the reuse algorithm: | ||
only segments smaller than the default segment size (which will |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not completely clear to me what the "default segment size" is referring to. I assume it's the "minimum" size of segments returned by the segment allocator; the 64KB carved out of some other, larger segment in the example above. Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's correct; i'll see if I can make that more obvious
free segments, which are `TR::MemorySegment`s that have already | ||
been cut up into the default allocation size. Because all | ||
allocation requests are rounded up to this size anyway (except | ||
for larger memory requests), any segment on this free list can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the phrasing of this sentence slightly confusing because, if I understand correctly, larger memory requests are rounded up to a multiple of the base size. Can the sentence be changed to something like:
Because all allocation requests smaller than the base are rounded up, ...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll try to reword that phrase to make it less awkward
`TR::MemorySegment` allocated by the backing memory allocator | ||
that has not been fully consumed yet. If the request can | ||
be satisfied from this memory segment, then a new | ||
`TR::MemorySegment` will be created (using the raw allocator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might also be worth mentioning where the raw allocator is coming from. I assume it's the one inside the backing allocator but I'd prefer to have that explicitly stated for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes sense.
There are a couple more changes coming already, inspired by the work in OpenJ9. I would characterize these changes as mostly "cosmetic" in that they do not impact how the memory allocation classes operate; the changers make them more useful for reuse in other projects:
...probably more to come... |
This pull request no longer reflects the direction I think this work should take. Following the compiler architecture meeting where we discussed this design, I received significant input from others that motivated a somewhat different take. I also gained an improved understanding of how RawAllocator interacts with STL allocators that makes some of this design, as written, problematic. I'm going to close this PR at this point, since it's obviously stale. Note to any (optimistically presumed) subsequent readers: please feel free to contact me to discuss this work if you're interested :) . |
This PR prereqs a change in OpenJ9 to avoid breaking OpenJ9, so
it has been marked WIP. Reviews are welcome at this stage.
The lowest level memory allocation classes in the OMR compiler
have a complicated hierarchy and relationship that many people
have struggled to understand and maintain. The complexity came
about trying to hold a unified picture of memory allocation
between OMR (which just uses a raw malloc allocator) and OpenJ9
(which uses J9MemorySegments allocated from the VM a.k.a.
"the system"). Unfortunately, this unity did not materialize
and so these lower level allocation classes are not actually
shared with OpenJ9.
The set of changes in this commit is motivated by the effort
to try to compile the basic JitBuilder files in compiler/ilgen
as part of OpenJ9. In this commit, I brought over the code from
OpenJ9's SystemSegmentProvider and refactored it so that it
could operate in OMR using TR::MemorySegments rather than
J9MemorySegments. I changed its name to SegmentAllocator and
declare the SegmentAllocator as the base layer for the higher
level allocation support for e.g. regions and STL and other
objects in the compiler component.
This commit is the first stage in a series of coordinated
changes to OMR and OpenJ9 to migrate from the "old" memory
allocation scheme to the "new" memor allocation scheme. In
this change, most of the new code is implemented inside
a new compiler/env/mem directory which contains any files
from compiler/env that required substantial modification.
This commit also introduces a temporary macro called,
uninspiringly, OLD_MEMORY: if OLD_MEMORY is defined then
the old OMR memory allocation classes will be built rather
than the new ones. By default after this commit, OMR will
build with the new memory allocation classes (e.g. into
jitbuilder and compilertest). Most consumers of the OMR
compiler should experience zero functional differences
as the memory classes being changed are at the lowest levels
of the compiler implementation (well lower than the facilities
most compiler developers use directly).
The SegmentAllocator will hold a BackingMemoryAllocator and a
RawAllocator. In fact, the BackingMemoryAllocator in OMR is
an only slightly beefed up RawAllocator which returns memory
wrapped in a TR::MemorySegment. I deleted the SegmentPool code
because nobody was using it. The DebugSegmentProvider code has
been moved into DebugRawAllocator (subclass of TR::RawAllocator)
as well as DebugSegmentAllocator (subclass of TR::SegmentAllocator).
I added some tracing output to the SegmentAllocator and
BackingMemoryAllocator to track segments as they are allocated
and deallocated (see MEMLOG macros in each file). This debug
output uses printfs due to the primordial environment of the
memory allocators, and is activated by environment variables
OMRDebug_BackingMemory=1 or 2 or 3 (increasing detail)
OMRDebug_SegmetnAllocator=1 or 2 or 3 (increasing detail)
While debugging the memory allocation layers, I discovered that
the original list management for segments was not very efficient
in that new segments were typically added at the end of lists but
searched starting from the beginning. This commit changes the
order of search to require fewer probes (less than half the
probes required in some very unscientific studies). I doubt
it will have any performance impact in most workloads but
it felt better to fix it.
BackingMemoryAllocator, SegmentAllocator, and DebugSegmentAllocator
are all extensible classes to facilitate a similar refactoring
extravaganza to be inflicted upon OpenJ9 very soon.