-
Notifications
You must be signed in to change notification settings - Fork 17
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
Memory mapped files for parsing storage (proposal for comment) #33
Comments
By Andrew Johnson on Apr 09, 2021 13:15 Interesting. |
By Jason Koch on Apr 10, 2021 13:30
In my experience this depends on the 'shape' of the heap in analysis. I'll add example calcs for 1billion object count, very rough analysis. The primary determinant of memory usage is the object count, and to a smaller extent the tree structure from GC roots. Much less important is the size of the hprof.
For 1 billion objects, 20GB during generation, falling to 8GB steady state. Bulk of memory here is the 'Identifier' store which is a long[] at least as long as the count of objects. While generating, up to newCapacity is allocated as copy/scratch space (1.5x), so have to assume 2.5x8BN objects.
Multiple indexes generated here, assuming 1bn objects. Likely to use 16-28GB, plus scratch space; majority retained. (1) object id -> outbound id references log [IntArray1NWriter, flush to PosIndexStreamer/IntIndexStreamer] (depends on shape of ref tree, int[] 4GB for header, byte[] 1GB for header2, plus IntIndexStreamer underneath, which uses int[page size] during generation, plus background compression. +12GB.
Approx 25GB here during processing. Most memory dropped after complete. Over and above whatever is stored previously, for 1billion objects:
Assuming a full 1bn heap, approx 28GB during processing, dropped after complete; but, up to 16GB of index is unload()ed prior to starting which mitigates this quite a bit. a2size, o2address, o2class are unload()ed here, so effective requirement is somewhere less than 28GB, we can assume somewhere 8-16GB is saved before starting, depending on xxCompressed page effectiveness. Then, allocates 7x int[] for number of objects in the heap. For a 1bn object file, this is 7x4Bx1BN = 28GB. As you point out, if GarbageCleaner has reduced the footprint, DominatorTree requirements are scaled down respectively. ====== So, Identifier and Size certainly have an immediate impact. If we think this strategy is sensible we can extend the memory mapped strategy to all index types and save significant amounts of memory during processing. I picked the two that were least coupled in the code to begin with, and that would have an impact, to demonstrate the basic idea. Lots of the existing code relies on the class hierarchy and cross-references between reader and writer, so I wanted some validation of the idea before pursuing further. There are additional benefits too if we take this further. For example, rather than reading a whole chunk of the file in SoftReference and unpacking it, the ArrayLongCompressed could be a wrapper/layer over the underlying store, and then the OS manages caching. This sort of thing I think can simplify the code quite a bit as we would no longer need to manage concurrent caching to achieve performance. |
By Jason Koch on Apr 10, 2021 17:35 To show the impact I ran a quick test against a modest sized heap. It is a ~25% reduction in memory required to process the heap. Sample heap:
Xmx-Xms settings required to pass heap:
From this it is possible to see the impact of Identifier & Size indexes moved to mmap/off-heap. Once other generated indexes had been unload()ed, the next biggest use of memory is DominatorTree. |
By Andrew Johnson on Apr 28, 2021 09:07 A simple change for IndexWriter.Identifier might be to use the ArrayLongBig inside to collect identifiers with add. API Compatible changes: That might limit the size needed to 2n rather than 2.5 |
By Jason Koch on Apr 28, 2021 17:23 Yes, I looked initially at something like this, and it is viable. Specifically, you want to be able to release the heap allocation after the Identifier storage has been loaded. The biggest win would come from an API change or a sneaky trick. My thinking was, after a sort(), you could trigger a flush to disk and then reload the data from mmap'd region thus releasing it from the heap. Or, do it explicitly by exposing a flush in the API to convert it to a xxReader. However, as I built this out, it looked like going to a direct memory-mapped model made for much cleaner code, and allows a future path to simplify quite a bit of the storage and memory tier. I did not yet pursue it, but I could run an experiment w/ swapping the DominatorTree to off-heap (should be simple to swap to use an IntArray instead of int[], etc) and measure how that impacts overall parsing memory requirements. |
By Andrew Johnson on May 10, 2021 15:23 For Garbage Cleaner we have the IOne2LongIndex identifiers = idx.identifiers; index holding old object addresses. It is kept around for the unreachable objects phase, then there is the map[] phase where the old to new mapping is built, and a new array of object addresses, index by the new object ID. We can off-load the identifier address with quite a small code change. This should save quite a bit of space at the expense of another file write and read, but with the memory saved the SoftReference caches should work better too. |
By Andrew Johnson on May 11, 2021 04:55 Similarly, we could store the old object to class index to disk before the garbage clean. Example OutOfMemoryError when parsing a dump with 63 million live objects and presumably 63,169,600 total objects. at org.eclipse.mat.parser.internal.GarbageCleaner.clean(Lorg/eclipse/mat/parser/internal/PreliminaryIndexImpl;Lorg/eclipse/mat/parser/internal/SnapshotImplBuilder;Ljava/util/Map;Lorg/eclipse/mat/util/IProgressListener;)[I (GarbageCleaner.java:149) Object / Stack Frame |Name| Shallow Heap | Retained Heap
|
By Andrew Johnson on May 11, 2021 09:11 Created attachment 286362 The parsers can supply the object to address and object to class indexes as in-memory versions. This frees the memory by converting them to disk-backed versions. :notepad_spiral: GarbageCleaner-writeTo.patch |
By Jason Koch on May 11, 2021 09:12 Created Sample patch to support using disk-backed storage during DominatorTree construction. |
By Jason Koch on May 11, 2021 09:15 Yes, there are a few ways to reduce memory footprint ongoing. For ex the attached dominator tree patch provides reduction of 5x int[] from the DominatorTree construction - for a 2bn object heap, this works out to a saving of 40GB of required heap space. |
By Andrew Johnson on May 11, 2021 09:19 I've added a simple patch to free some space during garbage collection. Please give it a try on a large dump to see if it helps. |
May 12, 2021 11:58 New Gerrit change created: https://git.eclipse.org/r/c/mat/org.eclipse.mat/+/180543 |
May 12, 2021 12:01 Gerrit change https://git.eclipse.org/r/c/mat/org.eclipse.mat/+/180543 was merged to [master]. |
May 12, 2021 15:53 New Gerrit change created: https://git.eclipse.org/r/c/mat/org.eclipse.mat/+/180553 |
May 12, 2021 15:56 Gerrit change https://git.eclipse.org/r/c/mat/org.eclipse.mat/+/180553 was merged to [master]. |
By Andrew Johnson on May 13, 2021 03:09 I've merged some changes freeing up some space for object id to address and object id to class id during GC. |
By Andrew Johnson on Jun 02, 2021 09:31 Comment on Please do not merge this patch. |
By Andrew Johnson on Jun 02, 2021 09:31 Please don't take this as a vote against this idea, but I'm going to mark attachment 286363 as obsolete as it looks like includes code which is not original so can't be submitted under the Eclipse Contributor Agreement https://www.eclipse.org/legal/ECA.php as EPL code to the project. If it's obsolete it's a reminder not to merge this. |
By Jason Koch on Jun 02, 2021 12:09 +1 agree with your position to obsolete the dom-tree patch |
By Jason Koch on Jun 02, 2021 13:16 Created attachment 286508 Fix to place files into java tmp location instead of current working directory. :notepad_spiral: file_572512.txt |
By Jason Koch on Jun 02, 2021 13:18 Created attachment 286509 Explicitly check for negative indexes in case of greater than 2bn indexes (overflow) :notepad_spiral: file_572512.txt |
By Jason Koch on Jun 02, 2021 13:21 Created attachment 286510 Also, this basic heap-backed implementation could smooth any transition / migration work. :notepad_spiral: file_572512.txt |
By Andrew Johnson on May 19, 2022 04:07 See bug 571331 comment 4 for details of the Identifier change which reduces the memory usage to 16nOld in generation, 8nOld in use, so for 1 billion objects, 16GB during generation, falling to 8GB steady state. DTFJIndexBuilder shows how the write out the Identifier to a temporary index after pass 1. The reader then uses the standard MAT reader so uses SoftReference caching. We should do this for HPROF. We need a fix to bug 579931 to make reverse() thread safe. object id -> array size index is actually 4*nOld in size
The compression here is based on the maximum position in file, so for 1,000,000,000 objects and perhaps a 80GB file this should be less than 5GB. |
By Jason Koch on May 19, 2022 12:37 In my view, putting the arrays behind an abstraction of some sort is the primary benefit. We have two challenges, in my view, to supporting large heaps:
By building a small abstraction over int[] and long[] we can then use our own long-based indexing and grow/shrink the backing store is needed. Even if the backing store is "backed by an array", then we introduce flexibility to the design, such that users can even make the tradeoff themselves, or even go so far as to offer it as an osgi/eclipse plugin, etc. |
| --- | --- |
| Bugzilla Link | 572512 |
| Status | NEW |
| Importance | P3 normal |
| Reported | Apr 01, 2021 01:18 EDT |
| Modified | May 19, 2022 12:37 EDT |
| Version | 1.11 |
| See also | Gerrit change 180543, Git commit 039199c8, Gerrit change 180553, Git commit 542be78a |
| Reporter | Jason Koch |
Description
Created attachment 286016
mmap_index_writer_base.patch
Current parsing of files requires reading large arrays into heap storage, which provides a minimum bound on the footprint of Pass1/Pass2 parsing.
This patch moves the parsing to off-heap storage, specifically to a file-backed region. This allows parsing larger files within a significantly smaller heap footprint.
Observations:
Comments:
:notepad_spiral: mmap_index_writer_base.patch
The text was updated successfully, but these errors were encountered: