RFR(XL): 8220310: Implementation: NUMA-Aware Memory Allocation for G1, Mutator (1/3)

Thomas Schatzl thomas.schatzl at oracle.com
Fri Oct 4 12:11:42 UTC 2019

Hi Sangheon,

   thanks for your  hard work on this!

On 01.10.19 18:43, sangheon.kim at oracle.com wrote:
> Hi Kim and others,
> This webrev.2 simplified a bit more after changing 'heap expansion' 
> approach.
> webrev:
> http://cr.openjdk.java.net/~sangheki/8220310/webrev.2/
> http://cr.openjdk.java.net/~sangheki/8220310/webrev.2.inc
> Testing: hs-tier1 ~ 5 +-UseNUMA


- os_solaris.cpp:2236: indentation addition

- os_windows.cpp: os::get_address_id(): the "return 0" is in the same 
line as the method declaration, while the change uses extra lines in 
os_bsd. Please make this uniform.

g1_globals.cpp: unnecessary whitespace  change

- G1Allocator::unsafe_max_alloc(): I need to think some more if this is 
correct - should that really be node specific? (and not the max of all 

Otoh I think this is fine.

- g1Allocator::used_in_alloc_regions(): not sure why the assert has been 

- os_linux.cpp: os::numa_get_address_id(): I think "id" should be an 
"int", not uint32_t according to 

And I think you can initialize it with os::InvalidId;

- in G1Allocator::current_node_index() retrieving the current node index 
is part of the G1MemoryNodeManager; I would really prefer if this were 
some property of a Thread. Not sure what others think.

That value could be put into the G1ThreadLocalData.

In any case, G1Allocator should probably cache the reference to the 
G1MemoryNodeManager for faster access.

- G1CollectedHeap::expand_single_region(): the log output in the first 
line looks more like some debug code than generally interesting information.

- G1CollectedHeap::expand_single_region(): pre-existing: it should add 
to the in-safepoint expansion time like expand(); okay to just file a CR.

- instead of the "late initialization method" set_page_size() I would 
prefer to have this value passed in the constructor. It is not required 
to me to have the create() call in the initialization list of 
g1CollectedHeap at all costs... it could be put right after we determine 
the page size in the body of the G1CollectedHeap constructor.

- g1CollectedHeap.hpp:940: no need to delete the newline.

- g1MemoryNodeManager.cpp:41: that comment does not add information imho

- G1NUMA::index_of_current_thread() needs a comment

- G1NUMA::index_of_num_id/is_valid_numa_id/ should be private

- not sure why G1NUMA::initialize()/set_numa() are needed. It's only 
call is right after instantiating a G1NUMA instance

- G1NUMA::request_memory_on_node needs a comment.

- I observed that a *lot* of G1NUMA methods are only used by 
G1MemoryNodeManager; and G1MemoryNodeManager just forwards to G1NUMA a 
lot. Maybe these two can be merged?

- G1NUMA::preferred_index_for_address/request_memory_on_node: I would 
prefer if these methods were not hardcoded with HeapRegion metrics as 

I.e. for preferred_index_for_address(), instead of the address it is 
probably better to pass it the zero-based index directly, that is used 
for calculating the node index. I.e. all callers know the HeapRegion's 
index anyway *and* this would make the method independent of 

I.e. something like preferred_node_index_for_index(<region-index>), 
because then the same method can be reused for other data structures 
than the heap/heap region.

G1NUMA::request_memory_on_node() could also be moved to 
G1PageBasedVirtualSpace, using the chunk sizes of page based virtual 
space instead of hardcoding HeapRegion::GrainBytes (i.e. hardcode the 
method to HeapRegion) - or pass in the "chunk size" calculated there 
from G1PageBasedVirtualSize.

I think this would increase the generality and usefulness of 
G1NUMA/G1MemoryNodeManager a lot without "passing in too many node 
indices everywhere".

- G1PageBasedVirtualSpace: the _numa member seems to be used exactly in 
one method where performance does not look critical. Maybe it is better 
to reference it directly there via G1CollectedHeap.

- heapRegionManager.cpp:verfiy_actual_node_index(): not sure if that 
should be debug level.

I would also kind of prefer a method that iterates over all regions and 
prints a summary status (and potentially drop this per-region checking 
at least when allocating a new free region). It is sufficient to print a 
summary of expected/actual values, at most a summary per node. I.e.

"NUMA Node index verification: Nodes: X_0/Y_0 X_1/Y_1 ... X_N/Y_N 
Unknown: Z Total: X/Y"

where X(_i) is the number of matching indexes (for node index i) and 
Y(_i) the number of expected (for node index i).

Also, the correct word to use here is "mismatch" not "different" (to what?)

- in some discussion we talked about the "node_index" lifecycle, and 
what I remember is the following:

   - initially, when we commit/make the region available, we set that 
HeapRegion's node_index to "Unknown" (with AlwaysPretouch on we can of 
course immediately set the correct one).
   - in HeapRegion::node_index() we do something like the following 

     if (_node_index == Unknown) {
       // try to get actual node index from OS, and update _node_index 
if we could get the information

     if (_node_index == Unknown) { // Still unknown
       // return _preferred_ node index *without* updating _node_index
     return _node_index;

   - now, during the "verification" pass, we use whether 
HeapRegion::node_index() == preferred_node_index to determine if the 
region is on the correct node.

The change only sets the node index during making the region available, 
and immediately to the preferred node index.

I.e. we eventually end up with the actual node index reported by the OS 
in HeapRegion::_node_index.

- for the expression "G1MemoryNodeManager::num_active_nodes() > 1" it 
would be nice to have an extra method in G1MemoryNodeManager instead of 
repeating it over and over.

- heapRegionManager.cpp:print_node_id_of_regions: that method will print 
a huge amount of lines. Better to print the summary I sketched out above.

- in FreeRegionList::remove_region_with_node_index(), the maximum search 
depth must take into account how many regions are there per page.

Consider 1GB pages, 32M region size, meaning that we get 32 consecutive 
Now with a node amount of 2, the maximum search depth will be 6 - which 
is too low :)
The intention is probably 3  * MAX(page_size / region size, 1) * 

I think it is useful to put that expresssion into 
G1NUMA/G1NodeMemoryManager (or somewhere else appropriate - 
HeapRegionManager?) to avoid that part having too much info about page size.

- os.hpp: the new Enum values might or might need some description.

Btw, there is no regression in performance from the .0/.1 versions of 
this code in our benchmarks.


More information about the hotspot-runtime-dev mailing list