--- old/src/hotspot/os/linux/os_linux.cpp 2019-11-04 19:56:16.740921487 -0800 +++ new/src/hotspot/os/linux/os_linux.cpp 2019-11-04 19:56:16.356921475 -0800 @@ -3008,17 +3008,13 @@ } int os::numa_get_group_id_for_address(const void* address) { -#ifndef MPOL_F_NODE -#define MPOL_F_NODE (1<<0) // Return next IL mode instead of node mask -#endif - -#ifndef MPOL_F_ADDR -#define MPOL_F_ADDR (1<<1) // Look up VMA using address -#endif + void** pages = const_cast(&address); + int id = -1; - int id = 0; - - if (syscall(SYS_get_mempolicy, &id, NULL, 0, const_cast(address), MPOL_F_NODE | MPOL_F_ADDR) == -1) { + if (os::Linux::numa_move_pages(0, 1, pages, NULL, &id, 0) == -1) { + return -1; + } + if (id < 0) { return -1; } return id; @@ -3152,6 +3148,8 @@ libnuma_v2_dlsym(handle, "numa_get_membind"))); set_numa_get_interleave_mask(CAST_TO_FN_PTR(numa_get_interleave_mask_func_t, libnuma_v2_dlsym(handle, "numa_get_interleave_mask"))); + set_numa_move_pages(CAST_TO_FN_PTR(numa_move_pages_func_t, + libnuma_dlsym(handle, "numa_move_pages"))); if (numa_available() != -1) { set_numa_all_nodes((unsigned long*)libnuma_dlsym(handle, "numa_all_nodes")); @@ -3286,6 +3284,7 @@ os::Linux::numa_distance_func_t os::Linux::_numa_distance; os::Linux::numa_get_membind_func_t os::Linux::_numa_get_membind; os::Linux::numa_get_interleave_mask_func_t os::Linux::_numa_get_interleave_mask; +os::Linux::numa_move_pages_func_t os::Linux::_numa_move_pages; os::Linux::NumaAllocationPolicy os::Linux::_current_numa_policy; unsigned long* os::Linux::_numa_all_nodes; struct bitmask* os::Linux::_numa_all_nodes_ptr; --- old/src/hotspot/os/linux/os_linux.hpp 2019-11-04 19:56:17.940921524 -0800 +++ new/src/hotspot/os/linux/os_linux.hpp 2019-11-04 19:56:17.556921512 -0800 @@ -216,6 +216,7 @@ typedef void (*numa_interleave_memory_v2_func_t)(void *start, size_t size, struct bitmask* mask); typedef struct bitmask* (*numa_get_membind_func_t)(void); typedef struct bitmask* (*numa_get_interleave_mask_func_t)(void); + typedef long (*numa_move_pages_func_t)(int pid, unsigned long count, void **pages, const int *nodes, int *status, int flags); typedef void (*numa_set_bind_policy_func_t)(int policy); typedef int (*numa_bitmask_isbitset_func_t)(struct bitmask *bmp, unsigned int n); @@ -234,6 +235,7 @@ static numa_distance_func_t _numa_distance; static numa_get_membind_func_t _numa_get_membind; static numa_get_interleave_mask_func_t _numa_get_interleave_mask; + static numa_move_pages_func_t _numa_move_pages; static unsigned long* _numa_all_nodes; static struct bitmask* _numa_all_nodes_ptr; static struct bitmask* _numa_nodes_ptr; @@ -253,6 +255,7 @@ static void set_numa_distance(numa_distance_func_t func) { _numa_distance = func; } static void set_numa_get_membind(numa_get_membind_func_t func) { _numa_get_membind = func; } static void set_numa_get_interleave_mask(numa_get_interleave_mask_func_t func) { _numa_get_interleave_mask = func; } + static void set_numa_move_pages(numa_move_pages_func_t func) { _numa_move_pages = func; } static void set_numa_all_nodes(unsigned long* ptr) { _numa_all_nodes = ptr; } static void set_numa_all_nodes_ptr(struct bitmask **ptr) { _numa_all_nodes_ptr = (ptr == NULL ? NULL : *ptr); } static void set_numa_nodes_ptr(struct bitmask **ptr) { _numa_nodes_ptr = (ptr == NULL ? NULL : *ptr); } @@ -318,6 +321,9 @@ static int numa_distance(int node1, int node2) { return _numa_distance != NULL ? _numa_distance(node1, node2) : -1; } + static long numa_move_pages(int pid, unsigned long count, void **pages, const int *nodes, int *status, int flags) { + return _numa_move_pages != NULL ? _numa_move_pages(pid, count, pages, nodes, status, flags) : -1; + } static int get_node_by_cpu(int cpu_id); static int get_existing_num_nodes(); // Check if numa node is configured (non-zero memory node). --- old/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2019-11-04 19:56:19.016921557 -0800 +++ new/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2019-11-04 19:56:18.628921546 -0800 @@ -2391,6 +2391,15 @@ st->print("%u survivors (" SIZE_FORMAT "K)", survivor_regions, (size_t) survivor_regions * HeapRegion::GrainBytes / K); st->cr(); + if (_numa->is_enabled()) { + uint num_nodes = _numa->num_active_nodes(); + st->print(" remaining free region(s) on each NUMA node: "); + const int* node_ids = _numa->node_ids(); + for (uint node_index = 0; node_index < num_nodes; node_index++) { + st->print("%d=%u ", node_ids[node_index], _hrm->num_free_regions(node_index)); + } + st->cr(); + } MetaspaceUtils::print_on(st); } @@ -2580,6 +2589,20 @@ // We have just completed a GC. Update the soft reference // policy with the new heap occupancy Universe::update_heap_info_at_gc(); + + // Print NUMA statistics. + _numa->print_statistics(); +} + +void G1CollectedHeap::verify_numa_regions(const char* desc) { + LogTarget(Trace, gc, heap, verify) lt; + + if (lt.is_enabled()) { + LogStream ls(lt); + // Iterate all heap regions to print matching between preferred numa id and actual numa id. + NodeIndexCheckClosure cl(desc, _numa, &ls); + heap_region_iterate(&cl); + } } HeapWord* G1CollectedHeap::do_collection_pause(size_t word_size, @@ -2889,6 +2912,7 @@ } _verifier->verify_before_gc(type); _verifier->check_bitmaps("GC Start"); + verify_numa_regions("GC Start"); } void G1CollectedHeap::verify_after_young_collection(G1HeapVerifier::G1VerifyType type) { @@ -2899,6 +2923,7 @@ } _verifier->verify_after_gc(type); _verifier->check_bitmaps("GC End"); + verify_numa_regions("GC End"); } void G1CollectedHeap::expand_heap_after_young_collection(){ --- old/src/hotspot/share/gc/g1/g1CollectedHeap.hpp 2019-11-04 19:56:20.236921595 -0800 +++ new/src/hotspot/share/gc/g1/g1CollectedHeap.hpp 2019-11-04 19:56:19.852921583 -0800 @@ -530,6 +530,9 @@ // Merges the information gathered on a per-thread basis for all worker threads // during GC into global variables. void merge_per_thread_state_info(G1ParScanThreadStateSet* per_thread_states); + + void verify_numa_regions(const char* desc); + public: G1YoungRemSetSamplingThread* sampling_thread() const { return _young_gen_sampling_thread; } @@ -1284,7 +1287,9 @@ const G1SurvivorRegions* survivor() const { return &_survivor; } uint eden_regions_count() const { return _eden.length(); } + uint eden_regions_count(uint node_index) const { return _eden.regions_on_node(node_index); } uint survivor_regions_count() const { return _survivor.length(); } + uint survivor_regions_count(uint node_index) const { return _survivor.regions_on_node(node_index); } size_t eden_regions_used_bytes() const { return _eden.used_bytes(); } size_t survivor_regions_used_bytes() const { return _survivor.used_bytes(); } uint young_regions_count() const { return _eden.length() + _survivor.length(); } --- old/src/hotspot/share/gc/g1/g1EdenRegions.hpp 2019-11-04 19:56:21.380921630 -0800 +++ new/src/hotspot/share/gc/g1/g1EdenRegions.hpp 2019-11-04 19:56:20.996921619 -0800 @@ -25,6 +25,7 @@ #ifndef SHARE_GC_G1_G1EDENREGIONS_HPP #define SHARE_GC_G1_G1EDENREGIONS_HPP +#include "gc/g1/g1RegionsOnNodes.hpp" #include "gc/g1/heapRegion.hpp" #include "runtime/globals.hpp" #include "utilities/debug.hpp" @@ -35,18 +36,25 @@ // Sum of used bytes from all retired eden regions. // I.e. updated when mutator regions are retired. volatile size_t _used_bytes; + G1RegionsOnNodes _regions_on_node; public: - G1EdenRegions() : _length(0), _used_bytes(0) { } + G1EdenRegions() : _length(0), _used_bytes(0), _regions_on_node() { } - void add(HeapRegion* hr) { + virtual uint add(HeapRegion* hr) { assert(!hr->is_eden(), "should not already be set"); _length++; + return _regions_on_node.add(hr); } - void clear() { _length = 0; _used_bytes = 0; } + void clear() { + _length = 0; + _used_bytes = 0; + _regions_on_node.clear(); + } uint length() const { return _length; } + uint regions_on_node(uint node_index) const { return _regions_on_node.count(node_index); } size_t used_bytes() const { return _used_bytes; } --- old/src/hotspot/share/gc/g1/g1HeapTransition.cpp 2019-11-04 19:56:22.468921664 -0800 +++ new/src/hotspot/share/gc/g1/g1HeapTransition.cpp 2019-11-04 19:56:22.088921652 -0800 @@ -26,15 +26,38 @@ #include "gc/g1/g1CollectedHeap.hpp" #include "gc/g1/g1HeapTransition.hpp" #include "gc/g1/g1Policy.hpp" -#include "logging/log.hpp" +#include "logging/logStream.hpp" #include "memory/metaspace.hpp" -G1HeapTransition::Data::Data(G1CollectedHeap* g1_heap) { - _eden_length = g1_heap->eden_regions_count(); - _survivor_length = g1_heap->survivor_regions_count(); - _old_length = g1_heap->old_regions_count(); - _archive_length = g1_heap->archive_regions_count(); - _humongous_length = g1_heap->humongous_regions_count(); +G1HeapTransition::Data::Data(G1CollectedHeap* g1_heap) : + _eden_length(g1_heap->eden_regions_count()), + _survivor_length(g1_heap->survivor_regions_count()), + _old_length(g1_heap->old_regions_count()), + _archive_length(g1_heap->archive_regions_count()), + _humongous_length(g1_heap->humongous_regions_count()), + _eden_length_per_node(NULL), + _survivor_length_per_node(NULL) { + + uint node_count = G1NUMA::numa()->num_active_nodes(); + + if (node_count > 1) { + LogTarget(Debug, gc, heap, numa) lt; + + if (lt.is_enabled()) { + _eden_length_per_node = NEW_C_HEAP_ARRAY(uint, node_count, mtGC); + _survivor_length_per_node = NEW_C_HEAP_ARRAY(uint, node_count, mtGC); + + for (uint i = 0; i < node_count; i++) { + _eden_length_per_node[i] = g1_heap->eden_regions_count(i); + _survivor_length_per_node[i] = g1_heap->survivor_regions_count(i); + } + } + } +} + +G1HeapTransition::Data::~Data() { + FREE_C_HEAP_ARRAY(uint, _eden_length_per_node); + FREE_C_HEAP_ARRAY(uint, _survivor_length_per_node); } G1HeapTransition::G1HeapTransition(G1CollectedHeap* g1_heap) : _g1_heap(g1_heap), _before(g1_heap) { } @@ -84,6 +107,34 @@ } }; +static void log_regions(const char* msg, size_t before_length, size_t after_length, size_t capacity, + uint* before_per_node_length, uint* after_per_node_length) { + LogTarget(Info, gc, heap) lt; + + if (lt.is_enabled()) { + LogStream ls(lt); + + ls.print("%s regions: " SIZE_FORMAT "->" SIZE_FORMAT "(" SIZE_FORMAT ")", + msg, before_length, after_length, capacity); + // Not NULL only if gc+heap+numa at Debug level is enabled. + if (before_per_node_length != NULL && after_per_node_length != NULL) { + G1NUMA* numa = G1NUMA::numa(); + uint num_nodes = numa->num_active_nodes(); + const int* node_ids = numa->node_ids(); + ls.print(" ("); + for (uint i = 0; i < num_nodes; i++) { + ls.print("%d: %u->%u", node_ids[i], before_per_node_length[i], after_per_node_length[i]); + // Skip adding below if it is the last one. + if (i != num_nodes - 1) { + ls.print(", "); + } + } + ls.print(")"); + } + ls.print_cr(""); + } +} + void G1HeapTransition::print() { Data after(_g1_heap); @@ -106,12 +157,12 @@ after._humongous_length, usage._humongous_region_count); } - log_info(gc, heap)("Eden regions: " SIZE_FORMAT "->" SIZE_FORMAT "(" SIZE_FORMAT ")", - _before._eden_length, after._eden_length, eden_capacity_length_after_gc); + log_regions("Eden", _before._eden_length, after._eden_length, eden_capacity_length_after_gc, + _before._eden_length_per_node, after._eden_length_per_node); log_trace(gc, heap)(" Used: 0K, Waste: 0K"); - log_info(gc, heap)("Survivor regions: " SIZE_FORMAT "->" SIZE_FORMAT "(" SIZE_FORMAT ")", - _before._survivor_length, after._survivor_length, survivor_capacity_length_before_gc); + log_regions("Survivor", _before._survivor_length, after._survivor_length, survivor_capacity_length_before_gc, + _before._survivor_length_per_node, after._survivor_length_per_node); log_trace(gc, heap)(" Used: " SIZE_FORMAT "K, Waste: " SIZE_FORMAT "K", usage._survivor_used / K, ((after._survivor_length * HeapRegion::GrainBytes) - usage._survivor_used) / K); --- old/src/hotspot/share/gc/g1/g1HeapTransition.hpp 2019-11-04 19:56:23.592921699 -0800 +++ new/src/hotspot/share/gc/g1/g1HeapTransition.hpp 2019-11-04 19:56:23.208921687 -0800 @@ -39,7 +39,13 @@ size_t _humongous_length; const metaspace::MetaspaceSizesSnapshot _meta_sizes; + // Only includes current eden regions. + uint* _eden_length_per_node; + // Only includes current survivor regions. + uint* _survivor_length_per_node; + Data(G1CollectedHeap* g1_heap); + ~Data(); }; G1CollectedHeap* _g1_heap; --- old/src/hotspot/share/gc/g1/g1NUMA.cpp 2019-11-04 19:56:24.564921729 -0800 +++ new/src/hotspot/share/gc/g1/g1NUMA.cpp 2019-11-04 19:56:24.176921717 -0800 @@ -24,8 +24,7 @@ #include "precompiled.hpp" #include "gc/g1/g1NUMA.hpp" -#include "gc/g1/heapRegion.hpp" -#include "logging/log.hpp" +#include "logging/logStream.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" @@ -74,7 +73,7 @@ G1NUMA::G1NUMA() : _node_id_to_index_map(NULL), _len_node_id_to_index_map(0), _node_ids(NULL), _num_active_node_ids(0), - _region_size(0), _page_size(0) { + _region_size(0), _page_size(0), _stats(NULL) { } void G1NUMA::initialize_without_numa() { @@ -119,9 +118,12 @@ for (uint i = 0; i < _num_active_node_ids; i++) { _node_id_to_index_map[_node_ids[i]] = i; } + + _stats = new G1NUMAStats(_node_ids, _num_active_node_ids); } G1NUMA::~G1NUMA() { + delete _stats; FREE_C_HEAP_ARRAY(int, _node_id_to_index_map); FREE_C_HEAP_ARRAY(int, _node_ids); } @@ -215,7 +217,7 @@ assert(is_aligned(aligned_address, page_size()), "Given address (" PTR_FORMAT ") should be aligned.", p2i(aligned_address)); assert(is_aligned(size_in_bytes, page_size()), "Given size (" SIZE_FORMAT ") should be aligned.", size_in_bytes); - log_debug(gc, heap, numa)("Request memory [" PTR_FORMAT ", " PTR_FORMAT ") to be numa id (%d).", + log_trace(gc, heap, numa)("Request memory [" PTR_FORMAT ", " PTR_FORMAT ") to be NUMA id (%d)", p2i(aligned_address), p2i((char*)aligned_address + size_in_bytes), _node_ids[node_index]); os::numa_make_local((char*)aligned_address, size_in_bytes, _node_ids[node_index]); } @@ -225,3 +227,80 @@ // There would be some cases that 1 page may be consisted of multiple HeapRegions. return 3 * MAX2((uint)(page_size() / region_size()), (uint)1) * num_active_nodes(); } + +void G1NUMA::update_statistics(G1NUMAStats::NodeDataItems phase, + uint requested_node_index, + uint allocated_node_index) { + if (_stats == NULL) { + return; + } + + uint converted_req_index; + if(requested_node_index < _num_active_node_ids) { + converted_req_index = requested_node_index; + } else { + assert(requested_node_index == AnyNodeIndex, + "Requested node index %u should be AnyNodeIndex.", requested_node_index); + converted_req_index = _num_active_node_ids; + } + _stats->update(phase, converted_req_index, allocated_node_index); +} + +void G1NUMA::copy_statistics(G1NUMAStats::NodeDataItems phase, + uint requested_node_index, + size_t* allocated_stat) { + if (_stats == NULL) { + return; + } + + _stats->copy(phase, requested_node_index, allocated_stat); +} + +void G1NUMA::print_statistics() const { + if (_stats == NULL) { + return; + } + + _stats->print_statistics(); +} + +NodeIndexCheckClosure::NodeIndexCheckClosure(const char* desc, G1NUMA* numa, LogStream* ls) : + _desc(desc), _numa(numa), _ls(ls) { + + uint num_nodes = _numa->num_active_nodes(); + _matched = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC); + _mismatched = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC); + _total = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC); + memset(_matched, 0, sizeof(uint) * num_nodes); + memset(_mismatched, 0, sizeof(uint) * num_nodes); + memset(_total, 0, sizeof(uint) * num_nodes); +} + +NodeIndexCheckClosure::~NodeIndexCheckClosure() { + _ls->print("%s: NUMA region verification (id: matched/mismatched/total): ", _desc); + const int* numa_ids = _numa->node_ids(); + for (uint i = 0; i < _numa->num_active_nodes(); i++) { + _ls->print("%d: %u/%u/%u ", numa_ids[i], _matched[i], _mismatched[i], _total[i]); + } + + FREE_C_HEAP_ARRAY(uint, _matched); + FREE_C_HEAP_ARRAY(uint, _mismatched); + FREE_C_HEAP_ARRAY(uint, _total); +} + +bool NodeIndexCheckClosure::do_heap_region(HeapRegion* hr) { + // Preferred node index will only have valid node index. + uint preferred_node_index = _numa->preferred_node_index_for_index(hr->hrm_index()); + // Active node index may have UnknownNodeIndex. + uint active_node_index = _numa->index_of_address(hr->bottom()); + + if (preferred_node_index == active_node_index) { + _matched[preferred_node_index]++; + } else if (preferred_node_index != active_node_index && + active_node_index != G1NUMA::UnknownNodeIndex) { + _mismatched[preferred_node_index]++; + } + _total[preferred_node_index]++; + + return false; +} --- old/src/hotspot/share/gc/g1/g1NUMA.hpp 2019-11-04 19:56:25.548921759 -0800 +++ new/src/hotspot/share/gc/g1/g1NUMA.hpp 2019-11-04 19:56:25.168921747 -0800 @@ -25,10 +25,12 @@ #ifndef SHARE_VM_GC_G1_NUMA_HPP #define SHARE_VM_GC_G1_NUMA_HPP +#include "gc/g1/g1NUMAStats.hpp" +#include "gc/g1/heapRegion.hpp" #include "memory/allocation.hpp" #include "runtime/os.hpp" -class HeapRegion; +class LogStream; class G1NUMA: public CHeapObj { // Mapping of available node ids to 0-based index which can be used for @@ -49,6 +51,9 @@ // Necessary when touching memory. size_t _page_size; + // Stores statistic data. + G1NUMAStats* _stats; + size_t region_size() const; size_t page_size() const; @@ -113,6 +118,35 @@ // Returns maximum search depth which is used to limit heap region search iterations. // The number of active nodes, page size and heap region size are considered. uint max_search_depth() const; + + // Update the given phase of requested and allocated node index. + void update_statistics(G1NUMAStats::NodeDataItems phase, uint requested_node_index, uint allocated_node_index); + + // Copy all allocated statistics of the given phase and requested node. + // Precondition: allocated_stat should have same length of active nodes. + void copy_statistics(G1NUMAStats::NodeDataItems phase, uint requested_node_index, size_t* allocated_stat); + + // Print all statistics. + void print_statistics() const; +}; + +class NodeIndexCheckClosure : public HeapRegionClosure { + const char* _desc; + G1NUMA* _numa; + // Records matched count of each node. + uint* _matched; + // Records mismatched count of each node. + uint* _mismatched; + // Records total count of each node. + // Total = matched + mismatched + unknown. + uint* _total; + LogStream* _ls; + +public: + NodeIndexCheckClosure(const char* desc, G1NUMA* numa, LogStream* ls); + ~NodeIndexCheckClosure(); + + bool do_heap_region(HeapRegion* hr); }; #endif // SHARE_VM_GC_G1_NUMA_HPP --- old/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp 2019-11-04 19:56:26.684921794 -0800 +++ new/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp 2019-11-04 19:56:26.300921782 -0800 @@ -57,7 +57,9 @@ _stack_trim_lower_threshold(GCDrainStackTargetSize), _trim_ticks(), _old_gen_is_full(false), - _num_optional_regions(optional_cset_length) + _num_optional_regions(optional_cset_length), + _numa(g1h->numa()), + _obj_alloc_stat(NULL) { // We allocate number of young gen regions in the collection set plus one // entries, since entry 0 keeps track of surviving bytes for non-young regions. @@ -79,6 +81,8 @@ _closures = G1EvacuationRootClosures::create_root_closures(this, _g1h); _oops_into_optional_regions = new G1OopStarChunkedList[_num_optional_regions]; + + initialize_numa_stats(); } // Pass locally gathered statistics to global state. @@ -92,6 +96,7 @@ for (uint i = 0; i < length; i++) { surviving_young_words[i] += _surviving_young_words[i]; } + flush_numa_stats(); } G1ParScanThreadState::~G1ParScanThreadState() { @@ -99,6 +104,7 @@ delete _closures; FREE_C_HEAP_ARRAY(size_t, _surviving_young_words_base); delete[] _oops_into_optional_regions; + FREE_C_HEAP_ARRAY(size_t, _obj_alloc_stat); } size_t G1ParScanThreadState::lab_waste_words() const { @@ -248,6 +254,8 @@ return handle_evacuation_failure_par(old, old_mark); } } + update_numa_stats(node_index); + if (_g1h->_gc_tracer_stw->should_report_promotion_events()) { // The events are checked individually as part of the actual commit report_promotion_event(dest_attr, old, word_sz, age, obj_ptr, node_index); --- old/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp 2019-11-04 19:56:27.796921828 -0800 +++ new/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp 2019-11-04 19:56:27.412921817 -0800 @@ -95,6 +95,13 @@ size_t _num_optional_regions; G1OopStarChunkedList* _oops_into_optional_regions; + G1NUMA* _numa; + + // Records how many object allocations happened at each node during copy to survivor. + // Only starts recording when log of gc+heap+numa is enabled and its data is + // transferred when flushed. + size_t* _obj_alloc_stat; + public: G1ParScanThreadState(G1CollectedHeap* g1h, G1RedirtyCardsQueueSet* rdcqs, @@ -207,6 +214,12 @@ inline bool is_partially_trimmed() const; inline void trim_queue_to_threshold(uint threshold); + + // NUMA statistics related methods. + inline void initialize_numa_stats(); + inline void flush_numa_stats(); + inline void update_numa_stats(uint node_index); + public: oop copy_to_survivor_space(G1HeapRegionAttr const region_attr, oop const obj, markWord const old_mark); --- old/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp 2019-11-04 19:56:28.916921863 -0800 +++ new/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp 2019-11-04 19:56:28.532921851 -0800 @@ -230,4 +230,30 @@ return &_oops_into_optional_regions[hr->index_in_opt_cset()]; } +void G1ParScanThreadState::initialize_numa_stats() { + if (_numa->is_enabled()) { + LogTarget(Info, gc, heap, numa) lt; + + if (lt.is_enabled()) { + uint num_nodes = _numa->num_active_nodes(); + // Record only if there are multiple active nodes. + _obj_alloc_stat = NEW_C_HEAP_ARRAY(size_t, num_nodes, mtGC); + memset(_obj_alloc_stat, 0, sizeof(size_t) * num_nodes); + } + } +} + +void G1ParScanThreadState::flush_numa_stats() { + if (_obj_alloc_stat != NULL) { + uint node_index = _numa->index_of_current_thread(); + _numa->copy_statistics(G1NUMAStats::LocalObjProcessAtCopyToSurv, node_index, _obj_alloc_stat); + } +} + +void G1ParScanThreadState::update_numa_stats(uint node_index) { + if (_obj_alloc_stat != NULL) { + _obj_alloc_stat[node_index]++; + } +} + #endif // SHARE_GC_G1_G1PARSCANTHREADSTATE_INLINE_HPP --- old/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp 2019-11-04 19:56:29.976921896 -0800 +++ new/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp 2019-11-04 19:56:29.592921884 -0800 @@ -30,17 +30,23 @@ G1SurvivorRegions::G1SurvivorRegions() : _regions(new (ResourceObj::C_HEAP, mtGC) GrowableArray(8, true, mtGC)), - _used_bytes(0) {} + _used_bytes(0), + _regions_on_node() {} -void G1SurvivorRegions::add(HeapRegion* hr) { +uint G1SurvivorRegions::add(HeapRegion* hr) { assert(hr->is_survivor(), "should be flagged as survivor region"); _regions->append(hr); + return _regions_on_node.add(hr); } uint G1SurvivorRegions::length() const { return (uint)_regions->length(); } +uint G1SurvivorRegions::regions_on_node(uint node_index) const { + return _regions_on_node.count(node_index); +} + void G1SurvivorRegions::convert_to_eden() { for (GrowableArrayIterator it = _regions->begin(); it != _regions->end(); @@ -54,6 +60,7 @@ void G1SurvivorRegions::clear() { _regions->clear(); _used_bytes = 0; + _regions_on_node.clear(); } void G1SurvivorRegions::add_used_bytes(size_t used_bytes) { --- old/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp 2019-11-04 19:56:31.096921930 -0800 +++ new/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp 2019-11-04 19:56:30.712921918 -0800 @@ -25,6 +25,7 @@ #ifndef SHARE_GC_G1_G1SURVIVORREGIONS_HPP #define SHARE_GC_G1_G1SURVIVORREGIONS_HPP +#include "gc/g1/g1RegionsOnNodes.hpp" #include "runtime/globals.hpp" template @@ -35,17 +36,19 @@ private: GrowableArray* _regions; volatile size_t _used_bytes; + G1RegionsOnNodes _regions_on_node; public: G1SurvivorRegions(); - void add(HeapRegion* hr); + virtual uint add(HeapRegion* hr); void convert_to_eden(); void clear(); uint length() const; + uint regions_on_node(uint node_index) const; const GrowableArray* regions() const { return _regions; --- old/src/hotspot/share/gc/g1/heapRegion.cpp 2019-11-04 19:56:32.180921964 -0800 +++ new/src/hotspot/share/gc/g1/heapRegion.cpp 2019-11-04 19:56:31.792921952 -0800 @@ -462,9 +462,9 @@ if (UseNUMA) { G1NUMA* numa = G1NUMA::numa(); if (node_index() < numa->num_active_nodes()) { - st->print("|%02d", numa->numa_id(node_index())); + st->print("|%d", numa->numa_id(node_index())); } else { - st->print("|--"); + st->print("|-"); } } st->print_cr(""); --- old/src/hotspot/share/gc/g1/heapRegionManager.cpp 2019-11-04 19:56:33.264921997 -0800 +++ new/src/hotspot/share/gc/g1/heapRegionManager.cpp 2019-11-04 19:56:32.880921985 -0800 @@ -26,6 +26,7 @@ #include "gc/g1/g1Arguments.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1ConcurrentRefine.hpp" +#include "gc/g1/g1NUMAStats.hpp" #include "gc/g1/heapRegion.hpp" #include "gc/g1/heapRegionManager.inline.hpp" #include "gc/g1/heapRegionSet.inline.hpp" @@ -107,10 +108,11 @@ HeapRegion* HeapRegionManager::allocate_free_region(HeapRegionType type, uint requested_node_index) { HeapRegion* hr = NULL; bool from_head = !type.is_young(); + G1NUMA* numa = G1NUMA::numa(); - if (requested_node_index != G1NUMA::AnyNodeIndex && G1NUMA::numa()->is_enabled()) { + if (requested_node_index != G1NUMA::AnyNodeIndex && numa->is_enabled()) { // Try to allocate with requested node index. - hr = _free_list.remove_region_with_node_index(from_head, requested_node_index, NULL); + hr = _free_list.remove_region_with_node_index(from_head, requested_node_index); } if (hr == NULL) { @@ -122,6 +124,10 @@ if (hr != NULL) { assert(hr->next() == NULL, "Single region should not have next"); assert(is_available(hr->hrm_index()), "Must be committed"); + + if (numa->is_enabled() && hr->node_index() < numa->num_active_nodes()) { + numa->update_statistics(G1NUMAStats::NewRegionAlloc, requested_node_index, hr->node_index()); + } } return hr; --- old/src/hotspot/share/gc/g1/heapRegionManager.hpp 2019-11-04 19:56:34.388922032 -0800 +++ new/src/hotspot/share/gc/g1/heapRegionManager.hpp 2019-11-04 19:56:34.000922020 -0800 @@ -192,6 +192,10 @@ return _free_list.length(); } + uint num_free_regions(uint node_index) const { + return _free_list.length(node_index); + } + size_t total_free_bytes() const { return num_free_regions() * HeapRegion::GrainBytes; } --- old/src/hotspot/share/gc/g1/heapRegionSet.cpp 2019-11-04 19:56:35.492922066 -0800 +++ new/src/hotspot/share/gc/g1/heapRegionSet.cpp 2019-11-04 19:56:35.108922054 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" +#include "gc/g1/g1NUMA.hpp" #include "gc/g1/heapRegionRemSet.hpp" #include "gc/g1/heapRegionSet.inline.hpp" @@ -101,6 +102,9 @@ curr->set_next(NULL); curr->set_prev(NULL); curr->set_containing_set(NULL); + + decrease_length(curr->node_index()); + curr = next; } clear(); @@ -119,6 +123,10 @@ return; } + if (_node_info != NULL && from_list->_node_info != NULL) { + _node_info->add(from_list->_node_info); + } + #ifdef ASSERT FreeRegionListIterator iter(from_list); while (iter.more_available()) { @@ -220,6 +228,9 @@ remove(curr); count++; + + decrease_length(curr->node_index()); + curr = next; } @@ -267,6 +278,10 @@ _head = NULL; _tail = NULL; _last = NULL; + + if (_node_info!= NULL) { + _node_info->clear(); + } } void FreeRegionList::verify_list() { @@ -303,3 +318,41 @@ guarantee(_tail == NULL || _tail->next() == NULL, "_tail should not have a next"); guarantee(length() == count, "%s count mismatch. Expected %u, actual %u.", name(), length(), count); } + + +FreeRegionList::FreeRegionList(const char* name, HeapRegionSetChecker* checker): + HeapRegionSetBase(name, checker), + _node_info(G1NUMA::numa()->is_enabled() ? new NodeInfo() : NULL) { + + clear(); +} + +FreeRegionList::~FreeRegionList() { + if (_node_info != NULL) { + delete _node_info; + } +} + +FreeRegionList::NodeInfo::NodeInfo() : _numa(G1NUMA::numa()), _length_of_node(NULL), + _num_nodes(_numa->num_active_nodes()) { + assert(UseNUMA, "Invariant"); + + _length_of_node = NEW_C_HEAP_ARRAY(uint, _num_nodes, mtGC); +} + +FreeRegionList::NodeInfo::~NodeInfo() { + FREE_C_HEAP_ARRAY(uint, _length_of_node); +} + +void FreeRegionList::NodeInfo::clear() { + for (uint i = 0; i < _num_nodes; ++i) { + _length_of_node[i] = 0; + } +} + +void FreeRegionList::NodeInfo::add(NodeInfo* info) { + for (uint i = 0; i < _num_nodes; ++i) { + _length_of_node[i] += info->_length_of_node[i]; + } +} + --- old/src/hotspot/share/gc/g1/heapRegionSet.hpp 2019-11-04 19:56:36.480922096 -0800 +++ new/src/hotspot/share/gc/g1/heapRegionSet.hpp 2019-11-04 19:56:36.092922084 -0800 @@ -136,11 +136,33 @@ // add / remove one region at a time or concatenate two lists. class FreeRegionListIterator; +class G1NUMA; class FreeRegionList : public HeapRegionSetBase { friend class FreeRegionListIterator; private: + + // This class is only initialized if there are multiple active nodes. + class NodeInfo : public CHeapObj { + G1NUMA* _numa; + uint* _length_of_node; + uint _num_nodes; + + public: + NodeInfo(); + ~NodeInfo(); + + inline void increase_length(uint node_index); + inline void decrease_length(uint node_index); + + inline uint length(uint index) const; + + void clear(); + + void add(NodeInfo* info); + }; + HeapRegion* _head; HeapRegion* _tail; @@ -148,20 +170,23 @@ // time. It helps to improve performance when adding several ordered items in a row. HeapRegion* _last; + NodeInfo* _node_info; + static uint _unrealistically_long_length; inline HeapRegion* remove_from_head_impl(); inline HeapRegion* remove_from_tail_impl(); + inline void increase_length(uint node_index); + inline void decrease_length(uint node_index); + protected: // See the comment for HeapRegionSetBase::clear() virtual void clear(); public: - FreeRegionList(const char* name, HeapRegionSetChecker* checker = NULL): - HeapRegionSetBase(name, checker) { - clear(); - } + FreeRegionList(const char* name, HeapRegionSetChecker* checker = NULL); + ~FreeRegionList(); void verify_list(); @@ -182,8 +207,7 @@ HeapRegion* remove_region(bool from_head); HeapRegion* remove_region_with_node_index(bool from_head, - const uint requested_node_index, - uint* region_node_index); + uint requested_node_index); // Merge two ordered lists. The result is also ordered. The order is // determined by hrm_index. @@ -200,6 +224,9 @@ virtual void verify(); uint num_of_regions_in_range(uint start, uint end) const; + + using HeapRegionSetBase::length; + uint length(uint node_index) const; }; // Iterator class that provides a convenient way to iterate over the --- old/src/hotspot/share/gc/g1/heapRegionSet.inline.hpp 2019-11-04 19:56:37.584922130 -0800 +++ new/src/hotspot/share/gc/g1/heapRegionSet.inline.hpp 2019-11-04 19:56:37.188922118 -0800 @@ -95,6 +95,8 @@ _head = hr; } _last = hr; + + increase_length(hr->node_index()); } inline HeapRegion* FreeRegionList::remove_from_head_impl() { @@ -145,12 +147,14 @@ // remove() will verify the region and check mt safety. remove(hr); + + decrease_length(hr->node_index()); + return hr; } inline HeapRegion* FreeRegionList::remove_region_with_node_index(bool from_head, - const uint requested_node_index, - uint* allocated_node_index) { + uint requested_node_index) { assert(UseNUMA, "Invariant"); const uint max_search_depth = G1NUMA::numa()->max_search_depth(); @@ -202,11 +206,48 @@ } remove(cur); - if (allocated_node_index != NULL) { - *allocated_node_index = cur->node_index(); - } + decrease_length(cur->node_index()); return cur; } +inline void FreeRegionList::NodeInfo::increase_length(uint node_index) { + if (node_index < _num_nodes) { + _length_of_node[node_index] += 1; + } +} + +inline void FreeRegionList::NodeInfo::decrease_length(uint node_index) { + if (node_index < _num_nodes) { + assert(_length_of_node[node_index] > 0, + "Current length %u should be greater than zero for node %u", + _length_of_node[node_index], node_index); + _length_of_node[node_index] -= 1; + } +} + +inline uint FreeRegionList::NodeInfo::length(uint node_index) const { + return _length_of_node[node_index]; +} + +inline void FreeRegionList::increase_length(uint node_index) { + if (_node_info != NULL) { + return _node_info->increase_length(node_index); + } +} + +inline void FreeRegionList::decrease_length(uint node_index) { + if (_node_info != NULL) { + return _node_info->decrease_length(node_index); + } +} + +inline uint FreeRegionList::length(uint node_index) const { + if (_node_info != NULL) { + return _node_info->length(node_index); + } else { + return 0; + } +} + #endif // SHARE_GC_G1_HEAPREGIONSET_INLINE_HPP --- old/test/hotspot/jtreg/gc/g1/numa/TestG1NUMATouchRegions.java 2019-11-04 19:56:38.724922166 -0800 +++ new/test/hotspot/jtreg/gc/g1/numa/TestG1NUMATouchRegions.java 2019-11-04 19:56:38.344922154 -0800 @@ -111,8 +111,8 @@ // Each 'int' represents a numa id of single HeapRegion (bottom page). // e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system // Check the first set(2 regions) - // 0| ...omitted..| 00 - // 1| ...omitted..| 01 + // 0| ...omitted..| 0 + // 1| ...omitted..| 1 static void checkCase1Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception { StringBuilder sb = new StringBuilder(); @@ -121,7 +121,7 @@ sb.append("| .* | "); // Append page node id. - sb.append(String.format("%02d", memoryNodeIds[index])); + sb.append(memoryNodeIds[index]); output.shouldMatch(sb.toString()); } @@ -132,10 +132,10 @@ // printed multiple times for same numa id. // e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system // Check the first set(4 regions) - // 0| ...omitted..| 00 - // 1| ...omitted..| 00 - // 2| ...omitted..| 01 - // 3| ...omitted..| 01 + // 0| ...omitted..| 0 + // 1| ...omitted..| 0 + // 2| ...omitted..| 1 + // 3| ...omitted..| 1 static void checkCase2Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception { StringBuilder sb = new StringBuilder(); @@ -147,7 +147,7 @@ sb.append("| .* | "); // Append page node id. - sb.append(String.format("%02d", memoryNodeIds[index])); + sb.append(memoryNodeIds[index]); output.shouldMatch(sb.toString()); sb.setLength(0); --- /dev/null 2019-10-07 09:27:17.753004199 -0700 +++ new/src/hotspot/share/gc/g1/g1NUMAStats.cpp 2019-11-04 19:56:39.428922187 -0800 @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/g1/g1NUMAStats.hpp" +#include "logging/logStream.hpp" + +double G1NUMAStats::Stat::rate() const { + return _requested == 0 ? 0 : (double)_hit / _requested * 100; +} + +G1NUMAStats::NodeDataArray::NodeDataArray(uint num_nodes) { + guarantee(num_nodes > 1, "Number of nodes (%u) should be set", num_nodes); + + // The row represents the number of nodes. + _num_column = num_nodes; + // +1 for G1MemoryNodeManager::AnyNodeIndex. + _num_row = num_nodes + 1; + + _data = NEW_C_HEAP_ARRAY(size_t*, _num_row, mtGC); + for (uint row = 0; row < _num_row; row++) { + _data[row] = NEW_C_HEAP_ARRAY(size_t, _num_column, mtGC); + } + + clear(); +} + +G1NUMAStats::NodeDataArray::~NodeDataArray() { + for (uint row = 0; row < _num_row; row++) { + FREE_C_HEAP_ARRAY(size_t, _data[row]); + } + FREE_C_HEAP_ARRAY(size_t*, _data); +} + +void G1NUMAStats::NodeDataArray::create_hit_rate(Stat* result) const { + size_t requested = 0; + size_t hit = 0; + + for (size_t row = 0; row < _num_row; row++) { + for (size_t column = 0; column < _num_column; column++) { + requested += _data[row][column]; + if (row == column) { + hit += _data[row][column]; + } + } + } + + assert(result != NULL, "Invariant"); + result->_hit = hit; + result->_requested = requested; +} + +void G1NUMAStats::NodeDataArray::create_hit_rate(Stat* result, uint req_index) const { + size_t requested = 0; + size_t hit = _data[req_index][req_index]; + + for (size_t column = 0; column < _num_column; column++) { + requested += _data[req_index][column]; + } + + assert(result != NULL, "Invariant"); + result->_hit = hit; + result->_requested = requested; +} + +size_t G1NUMAStats::NodeDataArray::sum(uint req_index) const { + size_t sum = 0; + for (size_t column = 0; column < _num_column; column++) { + sum += _data[req_index][column]; + } + + return sum; +} + +void G1NUMAStats::NodeDataArray::increase(uint req_index, uint alloc_index) { + assert(req_index < _num_row, + "Requested index %u should be less than the row size %u", + req_index, _num_row); + assert(alloc_index < _num_column, + "Allocated index %u should be less than the column size %u", + alloc_index, _num_column); + _data[req_index][alloc_index] += 1; +} + +void G1NUMAStats::NodeDataArray::clear() { + for (uint row = 0; row < _num_row; row++) { + memset((void*)_data[row], 0, sizeof(size_t) * _num_column); + } +} + +size_t G1NUMAStats::NodeDataArray::get(uint req_index, uint alloc_index) { + return _data[req_index][alloc_index]; +} + +void G1NUMAStats::NodeDataArray::copy(uint req_index, size_t* stat) { + assert(stat != NULL, "Invariant"); + + for (uint column = 0; column < _num_column; column++) { + _data[req_index][column] += stat[column]; + } +} + +G1NUMAStats::G1NUMAStats(const int* node_ids, uint num_node_ids) : + _node_ids(node_ids), _num_node_ids(num_node_ids), _node_data() { + + assert(_num_node_ids > 1, "Should have more than one active memory nodes %u", _num_node_ids); + + for (int i = 0; i < NodeDataItemsSentinel; i++) { + _node_data[i] = new NodeDataArray(_num_node_ids); + } +} + +G1NUMAStats::~G1NUMAStats() { + for (int i = 0; i < NodeDataItemsSentinel; i++) { + delete _node_data[i]; + } +} + +void G1NUMAStats::clear(G1NUMAStats::NodeDataItems phase) { + _node_data[phase]->clear(); +} + +void G1NUMAStats::update(G1NUMAStats::NodeDataItems phase, + uint requested_node_index, + uint allocated_node_index) { + _node_data[phase]->increase(requested_node_index, allocated_node_index); +} + +void G1NUMAStats::copy(G1NUMAStats::NodeDataItems phase, + uint requested_node_index, + size_t* allocated_stat) { + _node_data[phase]->copy(requested_node_index, allocated_stat); +} + +static const char* phase_to_explanatory_string(G1NUMAStats::NodeDataItems phase) { + switch(phase) { + case G1NUMAStats::NewRegionAlloc: + return "Placement match ratio"; + case G1NUMAStats::LocalObjProcessAtCopyToSurv: + return "Worker task locality match ratio"; + default: + return ""; + } +} + +#define RATE_TOTAL_FORMAT "%0.0f%% " SIZE_FORMAT "/" SIZE_FORMAT + +void G1NUMAStats::print_info(G1NUMAStats::NodeDataItems phase) { + LogTarget(Info, gc, heap, numa) lt; + + if (lt.is_enabled()) { + LogStream ls(lt); + Stat result; + size_t array_width = _num_node_ids; + + _node_data[phase]->create_hit_rate(&result); + + ls.print("%s: " RATE_TOTAL_FORMAT " (", + phase_to_explanatory_string(phase), result.rate(), result._hit, result._requested); + + for (uint i = 0; i < array_width; i++) { + if (i != 0) { + ls.print(", "); + } + _node_data[phase]->create_hit_rate(&result, i); + ls.print("%d: " RATE_TOTAL_FORMAT, + _node_ids[i], result.rate(), result._hit, result._requested); + } + ls.print_cr(")"); + } +} + +void G1NUMAStats::print_mutator_alloc_stat_debug() { + LogTarget(Debug, gc, heap, numa) lt; + + if (lt.is_enabled()) { + LogStream ls(lt); + uint array_width = _num_node_ids; + + ls.print("Allocated NUMA ids "); + for (uint i = 0; i < array_width; i++) { + ls.print("%8d", _node_ids[i]); + } + ls.print_cr(" Total"); + + ls.print("Requested NUMA id "); + for (uint req = 0; req < array_width; req++) { + ls.print("%3d ", _node_ids[req]); + for (uint alloc = 0; alloc < array_width; alloc++) { + ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->get(req, alloc)); + } + ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->sum(req)); + ls.print_cr(""); + // Add padding to align with the string 'Requested NUMA id'. + ls.print(" "); + } + ls.print("Any "); + for (uint alloc = 0; alloc < array_width; alloc++) { + ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->get(array_width, alloc)); + } + ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->sum(array_width)); + ls.print_cr(""); + } +} + +void G1NUMAStats::print_statistics() { + print_info(NewRegionAlloc); + print_mutator_alloc_stat_debug(); + + print_info(LocalObjProcessAtCopyToSurv); +} --- /dev/null 2019-10-07 09:27:17.753004199 -0700 +++ new/src/hotspot/share/gc/g1/g1NUMAStats.hpp 2019-11-04 19:56:40.492922220 -0800 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_G1_NODE_TIMES_HPP +#define SHARE_VM_GC_G1_NODE_TIMES_HPP + +#include "memory/allocation.hpp" + +// Manages statistics of multi nodes. +class G1NUMAStats : public CHeapObj { + struct Stat { + // Hit count: if requested id equals to returned id. + size_t _hit; + // Total request count + size_t _requested; + + // Hit count / total request count + double rate() const; + }; + + // Holds data array which has a size of (node count) * (node count + 1) to + // represent request node * allocated node. The request node includes any node case. + // All operations are NOT thread-safe. + // The row index indicates a requested node index while the column node index + // indicates an allocated node index. The last row is for any node index request. + // E.g. (req, alloc) = (0,0) (1,0) (2,0) (0,1) (Any, 3) (0,2) (0,3) (0,3) (3,3) + // Allocated node index 0 1 2 3 Total + // Requested node index 0 1 1 1 2 5 + // 1 1 0 0 0 1 + // 2 1 0 0 0 1 + // 3 0 0 0 1 1 + // Any 0 0 0 1 1 + class NodeDataArray : public CHeapObj { + // The number of nodes. + uint _num_column; + // The number of nodes + 1 (for any node request) + uint _num_row; + // 2-dimension array that holds count of allocated / requested node index. + size_t** _data; + + public: + NodeDataArray(uint num_nodes); + ~NodeDataArray(); + + // Create Stat result of hit count, requested count and hit rate. + // The result is copied to the given result parameter. + void create_hit_rate(Stat* result) const; + // Create Stat result of hit count, requested count and hit rate of the given index. + // The result is copied to the given result parameter. + void create_hit_rate(Stat* result, uint req_index) const; + // Return sum of the given index. + size_t sum(uint req_index) const; + // Increase at the request / allocated index. + void increase(uint req_index, uint alloc_index); + // Clear all data. + void clear(); + // Return current value of the given request / allocated index. + size_t get(uint req_index, uint alloc_index); + // Copy values of the given request index. + void copy(uint req_index, size_t* stat); + }; + +public: + enum NodeDataItems { + // Statistics of a new region allocation. + NewRegionAlloc, + // Statistics of object processing during copy to survivor region. + LocalObjProcessAtCopyToSurv, + NodeDataItemsSentinel + }; + +private: + const int* _node_ids; + uint _num_node_ids; + + NodeDataArray* _node_data[NodeDataItemsSentinel]; + + void print_info(G1NUMAStats::NodeDataItems phase); + + void print_mutator_alloc_stat_debug(); + +public: + G1NUMAStats(const int* node_ids, uint num_node_ids); + ~G1NUMAStats(); + + void clear(G1NUMAStats::NodeDataItems phase); + + // Update the given phase of requested and allocated node index. + void update(G1NUMAStats::NodeDataItems phase, uint requested_node_index, uint allocated_node_index); + + // Copy all allocated statistics of the given phase and requested node. + // Precondition: allocated_stat should have same length of active nodes. + void copy(G1NUMAStats::NodeDataItems phase, uint requested_node_index, size_t* allocated_stat); + + void print_statistics(); +}; + +#endif // SHARE_VM_GC_G1_NODE_TIMES_HPP --- /dev/null 2019-10-07 09:27:17.753004199 -0700 +++ new/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp 2019-11-04 19:56:41.580922254 -0800 @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/g1/g1NUMA.hpp" +#include "gc/g1/g1RegionsOnNodes.hpp" +#include "gc/g1/heapRegion.hpp" + +G1RegionsOnNodes::G1RegionsOnNodes() : _count_per_node(NULL), _numa(G1NUMA::numa()) { + _count_per_node = NEW_C_HEAP_ARRAY(uint, _numa->num_active_nodes(), mtGC); + clear(); +} + +G1RegionsOnNodes::~G1RegionsOnNodes() { + FREE_C_HEAP_ARRAY(uint, _count_per_node); +} + +uint G1RegionsOnNodes::add(HeapRegion* hr) { + uint node_index = hr->node_index(); + + // Update only if the node index is valid. + if (node_index < _numa->num_active_nodes()) { + *(_count_per_node + node_index) += 1; + return node_index; + } + + return G1NUMA::UnknownNodeIndex; +} + +void G1RegionsOnNodes::clear() { + for (uint i = 0; i < _numa->num_active_nodes(); i++) { + _count_per_node[i] = 0; + } +} + +uint G1RegionsOnNodes::count(uint node_index) const { + return _count_per_node[node_index]; +} --- /dev/null 2019-10-07 09:27:17.753004199 -0700 +++ new/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp 2019-11-04 19:56:42.720922289 -0800 @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_G1_G1REGIONS_HPP +#define SHARE_VM_GC_G1_G1REGIONS_HPP + +#include "memory/allocation.hpp" + +class G1NUMA; +class HeapRegion; + +// Contains per node index region count +class G1RegionsOnNodes : public StackObj { + volatile uint* _count_per_node; + G1NUMA* _numa; + +public: + G1RegionsOnNodes(); + + ~G1RegionsOnNodes(); + + // Increase _count_per_node for the node of given heap region and returns node index. + uint add(HeapRegion* hr); + + void clear(); + + uint count(uint node_index) const; +}; + +#endif // SHARE_VM_GC_G1_G1REGIONS_HPP