< prev index next >

src/share/vm/gc_implementation/g1/g1ParScanThreadState.cpp

Print this page
rev 7471 : 8060025: Object copy time regressions after JDK-8031323 and JDK-8057536
Summary: Evaluate and improve object copy time by micro-optimizations and splitting out slow and fast paths aggressively.
Reviewed-by:
Contributed-by: Tony Printezis <tprintezis@twitter.com>, Thomas Schatzl <thomas.schatzl@oracle.com>

*** 36,45 **** --- 36,46 ---- _dcq(&g1h->dirty_card_queue_set()), _ct_bs(g1h->g1_barrier_set()), _g1_rem(g1h->g1_rem_set()), _hash_seed(17), _queue_num(queue_num), _term_attempts(0), + _tenuring_threshold(g1h->g1_policy()->tenuring_threshold()), _age_table(false), _scanner(g1h, rp), _strong_roots_time(0), _term_time(0) { _scanner.set_par_scan_thread_state(this); // we allocate G1YoungSurvRateNumRegions plus one entries, since // we "sacrifice" entry 0 to keep track of surviving bytes for
*** 57,66 **** --- 58,73 ---- _surviving_young_words = _surviving_young_words_base + PADDING_ELEM_NUM; memset(_surviving_young_words, 0, (size_t) real_length * sizeof(size_t)); _g1_par_allocator = G1ParGCAllocator::create_allocator(_g1h); + _dest[InCSetState::NotInCSet] = InCSetState::NotInCSet; + // The dest for Young is used when the objects are aged enough to + // need to be moved to the next space. + _dest[InCSetState::Young] = InCSetState::Old; + _dest[InCSetState::Old] = InCSetState::Old; + _start = os::elapsedTime(); } G1ParScanThreadState::~G1ParScanThreadState() { _g1_par_allocator->retire_alloc_buffers();
*** 148,203 **** dispatch_reference(ref); } } while (!_refs->is_empty()); } ! oop G1ParScanThreadState::copy_to_survivor_space(oop const old, markOop const old_mark) { ! size_t word_sz = old->size(); ! HeapRegion* from_region = _g1h->heap_region_containing_raw(old); // +1 to make the -1 indexes valid... ! int young_index = from_region->young_index_in_cset()+1; assert( (from_region->is_young() && young_index > 0) || (!from_region->is_young() && young_index == 0), "invariant" ); ! G1CollectorPolicy* g1p = _g1h->g1_policy(); ! uint age = old_mark->has_displaced_mark_helper() ? old_mark->displaced_mark_helper()->age() ! : old_mark->age(); ! GCAllocPurpose alloc_purpose = g1p->evacuation_destination(from_region, age, ! word_sz); ! AllocationContext_t context = from_region->allocation_context(); ! HeapWord* obj_ptr = _g1_par_allocator->allocate(alloc_purpose, word_sz, context); ! #ifndef PRODUCT ! // Should this evacuation fail? ! if (_g1h->evacuation_should_fail()) { ! if (obj_ptr != NULL) { ! _g1_par_allocator->undo_allocation(alloc_purpose, obj_ptr, word_sz, context); ! obj_ptr = NULL; ! } ! } ! #endif // !PRODUCT if (obj_ptr == NULL) { // This will either forward-to-self, or detect that someone else has // installed a forwarding pointer. return _g1h->handle_evacuation_failure_par(this, old); } ! oop obj = oop(obj_ptr); // We're going to allocate linearly, so might as well prefetch ahead. Prefetch::write(obj_ptr, PrefetchCopyIntervalInBytes); ! oop forward_ptr = old->forward_to_atomic(obj); if (forward_ptr == NULL) { Copy::aligned_disjoint_words((HeapWord*) old, obj_ptr, word_sz); ! // alloc_purpose is just a hint to allocate() above, recheck the type of region ! // we actually allocated from and update alloc_purpose accordingly ! HeapRegion* to_region = _g1h->heap_region_containing_raw(obj_ptr); ! alloc_purpose = to_region->is_young() ? GCAllocForSurvived : GCAllocForTenured; ! ! if (g1p->track_object_age(alloc_purpose)) { if (age < markOopDesc::max_age) { age++; } if (old_mark->has_displaced_mark_helper()) { // In this case, we have to install the mark word first, --- 155,252 ---- dispatch_reference(ref); } } while (!_refs->is_empty()); } ! HeapWord* G1ParScanThreadState::allocate_in_next_plab(in_cset_state_t const state, ! in_cset_state_t* dest, ! size_t word_sz, ! AllocationContext_t const context) { ! assert(state != InCSetState::NotInCSet, err_msg("Unexpected state: %u", state)); ! assert(*dest != InCSetState::NotInCSet, err_msg("Unexpected dest: %u", *dest)); ! ! // Right now we only have two types of regions (young / old) so ! // let's keep the logic here simple. We can generalize it when necessary. ! if (*dest == InCSetState::Young) { ! HeapWord* const obj_ptr = _g1_par_allocator->allocate(InCSetState::Old, ! word_sz, context); ! if (obj_ptr == NULL) { ! return NULL; ! } ! // Make sure that we won't attempt to copy any other objects out ! // of a survivor region (given that apparently we cannot allocate ! // any new ones) to avoid coming into this slow path. ! _tenuring_threshold = 0; ! *dest = InCSetState::Old; ! return obj_ptr; ! } else { ! assert(*dest == InCSetState::Old, err_msg("Unexpected dest: %u", *dest)); ! // no other space to try. ! return NULL; ! } ! } ! ! in_cset_state_t G1ParScanThreadState::next_state(in_cset_state_t const state, markOop const m, uint& age) { ! if (state == InCSetState::Young) { ! age = !m->has_displaced_mark_helper() ? m->age() ! : m->displaced_mark_helper()->age(); ! if (age < _tenuring_threshold) { ! return state; ! } ! } ! return dest(state); ! } ! ! oop G1ParScanThreadState::copy_to_survivor_space(in_cset_state_t const state, ! oop const old, markOop const old_mark) { ! const size_t word_sz = old->size(); ! HeapRegion* const from_region = _g1h->heap_region_containing_raw(old); // +1 to make the -1 indexes valid... ! const int young_index = from_region->young_index_in_cset()+1; assert( (from_region->is_young() && young_index > 0) || (!from_region->is_young() && young_index == 0), "invariant" ); ! const AllocationContext_t context = from_region->allocation_context(); ! ! uint age = 0; ! in_cset_state_t dest_state = next_state(state, old_mark, age); ! HeapWord* obj_ptr = _g1_par_allocator->plab_allocate(dest_state, word_sz, context); + // PLAB allocations should succeed most of the time, so we'll + // normally check against NULL once and that's it. + if (obj_ptr == NULL) { + obj_ptr = _g1_par_allocator->allocate_direct_or_new_plab(dest_state, word_sz, context); + if (obj_ptr == NULL) { + obj_ptr = allocate_in_next_plab(state, &dest_state, word_sz, context); if (obj_ptr == NULL) { // This will either forward-to-self, or detect that someone else has // installed a forwarding pointer. return _g1h->handle_evacuation_failure_par(this, old); } + } + } ! assert(obj_ptr != NULL, "when we get here, allocation should have succeeded"); ! #ifndef PRODUCT ! // Should this evacuation fail? ! if (_g1h->evacuation_should_fail()) { ! // Doing this after all the allocation attempts also tests the ! // undo_allocation() method too. ! _g1_par_allocator->undo_allocation(dest_state, obj_ptr, word_sz, context); ! return _g1h->handle_evacuation_failure_par(this, old); ! } ! #endif // !PRODUCT // We're going to allocate linearly, so might as well prefetch ahead. Prefetch::write(obj_ptr, PrefetchCopyIntervalInBytes); ! const oop obj = oop(obj_ptr); ! const oop forward_ptr = old->forward_to_atomic(obj); if (forward_ptr == NULL) { Copy::aligned_disjoint_words((HeapWord*) old, obj_ptr, word_sz); ! if (dest_state == InCSetState::Young) { if (age < markOopDesc::max_age) { age++; } if (old_mark->has_displaced_mark_helper()) { // In this case, we have to install the mark word first,
*** 213,245 **** } else { obj->set_mark(old_mark); } if (G1StringDedup::is_enabled()) { ! G1StringDedup::enqueue_from_evacuation(from_region->is_young(), ! to_region->is_young(), queue_num(), obj); } ! size_t* surv_young_words = surviving_young_words(); surv_young_words[young_index] += word_sz; if (obj->is_objArray() && arrayOop(obj)->length() >= ParGCArrayScanChunk) { // We keep track of the next start index in the length field of // the to-space object. The actual length can be found in the // length field of the from-space object. arrayOop(obj)->set_length(0); oop* old_p = set_partial_array_mask(old); push_on_queue(old_p); } else { ! // No point in using the slower heap_region_containing() method, ! // given that we know obj is in the heap. ! _scanner.set_region(_g1h->heap_region_containing_raw(obj)); obj->oop_iterate_backwards(&_scanner); } } else { ! _g1_par_allocator->undo_allocation(alloc_purpose, obj_ptr, word_sz, context); ! obj = forward_ptr; } - return obj; } --- 262,299 ---- } else { obj->set_mark(old_mark); } if (G1StringDedup::is_enabled()) { ! const bool is_from_young = state == InCSetState::Young; ! const bool is_to_young = dest_state == InCSetState::Young; ! assert(is_from_young == _g1h->heap_region_containing_raw(old)->is_young(), ! "sanity"); ! assert(is_to_young == _g1h->heap_region_containing_raw(obj)->is_young(), ! "sanity"); ! G1StringDedup::enqueue_from_evacuation(is_from_young, ! is_to_young, queue_num(), obj); } ! size_t* const surv_young_words = surviving_young_words(); surv_young_words[young_index] += word_sz; if (obj->is_objArray() && arrayOop(obj)->length() >= ParGCArrayScanChunk) { // We keep track of the next start index in the length field of // the to-space object. The actual length can be found in the // length field of the from-space object. arrayOop(obj)->set_length(0); oop* old_p = set_partial_array_mask(old); push_on_queue(old_p); } else { ! HeapRegion* const to_region = _g1h->heap_region_containing_raw(obj_ptr); ! _scanner.set_region(to_region); obj->oop_iterate_backwards(&_scanner); } + return obj; } else { ! _g1_par_allocator->undo_allocation(dest_state, obj_ptr, word_sz, context); ! return forward_ptr; } }
< prev index next >