blob: 9b5b2bf59285ec2b345332e939353b0694229ddb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Xiao-Feng Li, 2006/10/05
*/
#include "gen.h"
#include "../common/space_tuner.h"
#include <math.h>
#define NOS_COPY_RESERVE_DELTA (GC_BLOCK_SIZE_BYTES<<1)
/*Tune this number in case that MOS could be too small, so as to avoid or put off fall back.*/
POINTER_SIZE_INT MOS_RESERVE_SIZE;
POINTER_SIZE_INT DEFAULT_MOS_RESERVE_SIZE = 36*MB;
static float Tslow = 0.0f;
static POINTER_SIZE_INT SMax = 0;
static POINTER_SIZE_INT last_total_free_size = 0;
typedef struct Gen_Mode_Adaptor{
float gen_minor_throughput;
float nongen_minor_throughput;
/*for obtaining the gen minor collection throughput.*/
int gen_mode_trial_count;
float major_survive_ratio_threshold;
unsigned int major_repeat_count;
POINTER_SIZE_INT adapt_nos_size;
}Gen_Mode_Adaptor;
void gc_gen_mode_adapt_init(GC_Gen *gc)
{
gc->gen_mode_adaptor = (Gen_Mode_Adaptor*)STD_MALLOC( sizeof(Gen_Mode_Adaptor));
Gen_Mode_Adaptor* gen_mode_adaptor = gc->gen_mode_adaptor;
gen_mode_adaptor->gen_minor_throughput = 0.0f;
/*reset the nongen_minor_throughput: the first default nongen minor (maybe testgc)may caused the result
calculated to be zero. so we initial the value to 1.0f here. */
gen_mode_adaptor->nongen_minor_throughput = 1.0f;
gen_mode_adaptor->gen_mode_trial_count = 0;
gen_mode_adaptor->major_survive_ratio_threshold = 1.0f;
gen_mode_adaptor->major_repeat_count = 1;
gen_mode_adaptor->adapt_nos_size = min_nos_size_bytes;
}
static float mini_free_ratio(float k, float m)
{
/*fixme: the check should be proved!*/
if(m < 0.005f) m = 0.005f;
if(k > 100.f) k = 100.f;
float b = - (2 + 2 * k * m);
float c = k * m * m + 2 * m + 1;
float D = b * b - 4 * c;
if (D <= 0) {
//printf("output 0.8f from k: %5.3f, m: %5.3f\n", k, m);
return 0.8f;
}
float pm = sqrt (D) / 2 ;
float base = - b / 2 ;
float res = base - pm;
if (res > 1.f) res = 0.8f;
/*fixme: the check should be proved!*/
if (res < 0.0f) res = 0.8f;
//printf("output %5.3f from k: %5.3f, m: %5.3f\n", res, k, m);
return res;
}
#define MAX_MAJOR_REPEAT_COUNT 3
#define MAX_MINOR_TRIAL_COUNT 2
#define MAX_INT32 0x7fffffff
void gc_gen_mode_adapt(GC_Gen* gc, int64 pause_time)
{
if(GEN_NONGEN_SWITCH == FALSE) return;
Blocked_Space* nos = (Blocked_Space*)gc->nos;
Blocked_Space* mos = (Blocked_Space*)gc->mos;
Gen_Mode_Adaptor* gen_mode_adaptor = gc->gen_mode_adaptor;
POINTER_SIZE_INT mos_free_size = blocked_space_free_mem_size(mos);
POINTER_SIZE_INT nos_free_size = blocked_space_free_mem_size(nos);
POINTER_SIZE_INT total_free_size = mos_free_size + nos_free_size;
if(collect_is_major()) {
assert(!gc_is_gen_mode());
if(gen_mode_adaptor->major_survive_ratio_threshold != 0 && mos->survive_ratio > gen_mode_adaptor->major_survive_ratio_threshold){
if(gen_mode_adaptor->major_repeat_count > MAX_MAJOR_REPEAT_COUNT ){
gc->force_gen_mode = TRUE;
gc_set_gen_mode(TRUE);
gc->next_collect_force_major = FALSE;
return;
}else{
gen_mode_adaptor->major_repeat_count++;
}
}else{
gen_mode_adaptor->major_repeat_count = 1;
}
}else{
/*compute throughput*/
if(!collect_last_is_minor((GC*)gc)){
gen_mode_adaptor->nongen_minor_throughput = 1.0f;
}
if(gc->force_gen_mode){
if(pause_time!=0){
if(gen_mode_adaptor->gen_minor_throughput != 0)
gen_mode_adaptor->gen_minor_throughput = (gen_mode_adaptor->gen_minor_throughput + (float) nos_free_size/(float)pause_time)/2.0f;
else
gen_mode_adaptor->gen_minor_throughput =(float) nos_free_size/(float)pause_time;
}
}else{
if(pause_time!=0){
if(gen_mode_adaptor->gen_minor_throughput != 1.0f)
gen_mode_adaptor->nongen_minor_throughput = (gen_mode_adaptor->nongen_minor_throughput + (float) nos_free_size/(float)pause_time)/2.0f;
else
gen_mode_adaptor->nongen_minor_throughput = (float) nos_free_size/(float)pause_time;
}
}
if(gen_mode_adaptor->nongen_minor_throughput <= gen_mode_adaptor->gen_minor_throughput ){
if( !collect_last_is_minor((GC*)gc) ){
gen_mode_adaptor->major_survive_ratio_threshold = mos->survive_ratio;
}else if( !gc->force_gen_mode ){
gc->force_gen_mode = TRUE;
gen_mode_adaptor->gen_mode_trial_count = MAX_INT32;
}
}
if(gc->next_collect_force_major && !gc->force_gen_mode){
gc->next_collect_force_major = FALSE;
gc->force_gen_mode = TRUE;
gen_mode_adaptor->gen_mode_trial_count = 2;
}else if( collect_last_is_minor((GC*)gc) && gc->force_gen_mode){
gen_mode_adaptor->gen_mode_trial_count = MAX_INT32;
}
if(gc->force_gen_mode && (total_free_size <= ((float)min_nos_size_bytes) * 1.3 )){
gc->force_gen_mode = FALSE;
gc_set_gen_mode(FALSE);
gc->next_collect_force_major = TRUE;
gen_mode_adaptor->gen_mode_trial_count = 0;
return;
}
if( gc->force_gen_mode ){
assert( gen_mode_adaptor->gen_mode_trial_count >= 0);
gen_mode_adaptor->gen_mode_trial_count --;
if( gen_mode_adaptor->gen_mode_trial_count >= 0){
gc_set_gen_mode(TRUE);
return;
}
gc->force_gen_mode = FALSE;
gc->next_collect_force_major = TRUE;
gen_mode_adaptor->gen_mode_trial_count = 0;
}
}
gc_set_gen_mode(FALSE);
return;
}
struct Mspace;
void mspace_set_expected_threshold_ratio(Mspace* mos, float threshold_ratio);
static void gc_decide_next_collect(GC_Gen* gc, int64 pause_time)
{
Space* nos = (Space*)gc->nos;
Space* mos = (Space*)gc->mos;
float survive_ratio = 0.2f;
if( MOS_RESERVE_SIZE != 0)
DEFAULT_MOS_RESERVE_SIZE = MOS_RESERVE_SIZE;
POINTER_SIZE_INT mos_free_size = mos_free_space_size(mos);
/* for space free size computation, semispace may leave some nos space used. But we use a simple approximation here.
That is, we just use the totoal nos size as nos free size. This is important. We can't use real nos_free_space_size(), because
the whole algorithm here in gc_decide_next_collect() assumes total free size is reduced after every minor collection, and
can only be increased after major collection. Otherwise the algorithm is invalid. If we use nos_free_space_size(), we may
get an increased total free size after a minor collection. */
POINTER_SIZE_INT nos_free_size = space_committed_size(nos);
POINTER_SIZE_INT total_free_size = mos_free_size + nos_free_size;
if(collect_is_major()) gc->force_gen_mode = FALSE;
if(!gc->force_gen_mode){
/*Major collection:*/
if(collect_is_major()){
mos->time_collections += pause_time;
Tslow = (float)pause_time;
SMax = total_free_size;
/*If fall back happens, and nos_boundary reaches heap_ceiling, then we force major.*/
if( nos_free_size == 0)
gc->next_collect_force_major = TRUE;
else gc->next_collect_force_major = FALSE;
/*If major is caused by LOS, or collection kind is ALGO_MAJOR_EXTEND, all survive ratio is not updated.*/
extern Boolean mos_extended;
if((gc->cause != GC_CAUSE_LOS_IS_FULL) && !mos_extended ){
survive_ratio = (float)mos->period_surviving_size/(float)mos->committed_heap_size;
mos->survive_ratio = survive_ratio;
}
/* why do I set it FALSE here? because here is the only place where it's used. */
mos_extended = FALSE;
/*If there is no minor collection at all, we must give mos expected threshold a reasonable value.*/
if((gc->tuner->kind != TRANS_NOTHING) && (nos->num_collections == 0))
mspace_set_expected_threshold_ratio((Mspace *)mos, 0.5f);
/*If this major is caused by fall back compaction, we must give nos->survive_ratio
*a conservative and reasonable number to avoid next fall back.
*In fallback compaction, the survive_ratio of mos must be 1.*/
if(collect_is_fallback()) nos->survive_ratio = 1;
}
/*Minor collection:*/
else
{
/*Give a hint to mini_free_ratio. */
if(nos->num_collections == 1){
/*Fixme: This is only set for tuning the first warehouse!*/
Tslow = pause_time / gc->survive_ratio;
SMax = (POINTER_SIZE_INT)((float)(gc->committed_heap_size - gc->los->committed_heap_size) * ( 1 - gc->survive_ratio ));
last_total_free_size = gc->committed_heap_size - gc->los->committed_heap_size;
}
nos->time_collections += pause_time;
POINTER_SIZE_INT free_size_threshold;
POINTER_SIZE_INT minor_surviving_size = last_total_free_size - total_free_size;
/*If the first GC is caused by LOS, mos->last_alloced_size should be smaller than this minor_surviving_size
*Because the last_total_free_size is not accurate.*/
if(nos->num_collections != 1){
assert(minor_surviving_size == mos->last_alloced_size);
}
float k = Tslow * nos->num_collections/nos->time_collections;
float m = ((float)minor_surviving_size)*1.0f/((float)(SMax - DEFAULT_MOS_RESERVE_SIZE ));
float free_ratio_threshold = mini_free_ratio(k, m);
if(SMax > DEFAULT_MOS_RESERVE_SIZE )
free_size_threshold = (POINTER_SIZE_INT)(free_ratio_threshold * (SMax - DEFAULT_MOS_RESERVE_SIZE ) + DEFAULT_MOS_RESERVE_SIZE );
else
free_size_threshold = (POINTER_SIZE_INT)(free_ratio_threshold * SMax);
/* FIXME: if the total free size is lesser than threshold, the time point might be too late!
* Have a try to test whether the backup solution is better for specjbb.
*/
// if ((mos_free_size + nos_free_size + minor_surviving_size) < free_size_threshold) gc->next_collect_force_major = TRUE;
if ((mos_free_size + nos_free_size)< free_size_threshold) gc->next_collect_force_major = TRUE;
survive_ratio = (float)minor_surviving_size/(float)space_committed_size((Space*)nos);
nos->survive_ratio = survive_ratio;
/*For LOS_Adaptive*/
POINTER_SIZE_INT mos_committed_size = space_committed_size((Space*)mos);
POINTER_SIZE_INT nos_committed_size = space_committed_size((Space*)nos);
if(mos_committed_size + nos_committed_size > free_size_threshold){
POINTER_SIZE_INT mos_size_threshold;
mos_size_threshold = mos_committed_size + nos_committed_size - free_size_threshold;
float mos_size_threshold_ratio = (float)mos_size_threshold / (mos_committed_size + nos_committed_size);
mspace_set_expected_threshold_ratio((Mspace *)mos, mos_size_threshold_ratio);
}
}
gc->survive_ratio = (gc->survive_ratio + survive_ratio)/2.0f;
last_total_free_size = total_free_size;
}
gc_gen_mode_adapt(gc,pause_time);
return;
}
/* FIXME:: In this algorithm, it assumes NOS is a full forward space.
Semispace GC's NOS has survivor_area. Need careful rethinking.
But this algorithm so far can be a good approximation. */
Boolean gc_compute_new_space_size(GC_Gen* gc, POINTER_SIZE_INT* mos_size, POINTER_SIZE_INT* nos_size)
{
Blocked_Space* nos = (Blocked_Space*)gc->nos;
Blocked_Space* mos = (Blocked_Space*)gc->mos;
Space* los = gc->los;
POINTER_SIZE_INT new_nos_size;
POINTER_SIZE_INT new_mos_size;
POINTER_SIZE_INT curr_nos_size = space_committed_size((Space*)nos);
// POINTER_SIZE_INT used_nos_size = nos_used_space_size((Space*)nos);
POINTER_SIZE_INT used_mos_size = mos_used_space_size((Space*)mos);
POINTER_SIZE_INT total_size; /* total_size is no-LOS spaces size */
#ifdef STATIC_NOS_MAPPING
total_size = max_heap_size_bytes - space_committed_size(los);
#else
POINTER_SIZE_INT curr_heap_commit_end;
if(LOS_ADJUST_BOUNDARY) {
curr_heap_commit_end=(POINTER_SIZE_INT)gc->heap_start + LOS_HEAD_RESERVE_FOR_HEAP_BASE + gc->committed_heap_size;
assert(curr_heap_commit_end > (POINTER_SIZE_INT)mos->heap_start);
total_size = curr_heap_commit_end - (POINTER_SIZE_INT)mos->heap_start;
}else {/*LOS_ADJUST_BOUNDARY else */
curr_heap_commit_end = (nos->committed_heap_size)? (POINTER_SIZE_INT) nos->heap_start + nos->committed_heap_size:
(POINTER_SIZE_INT) mos->heap_start+mos->committed_heap_size;
total_size = curr_heap_commit_end - (POINTER_SIZE_INT) mos->heap_start;
}
#endif
assert(total_size >= used_mos_size);
POINTER_SIZE_INT total_free = total_size - used_mos_size;
/*If total free is smaller than one block, there is no room for us to adjust*/
if(total_free < GC_BLOCK_SIZE_BYTES) return FALSE;
POINTER_SIZE_INT nos_reserve_size;
if( MOS_RESERVE_SIZE == 0){
/*To reserve some MOS space to avoid fallback situation.
*But we need ensure nos has at least one block.
*We have such fomula here:
*NOS_SIZE + NOS_SIZE * anti_fall_back_ratio + NOS_SIZE * survive_ratio = TOTAL_FREE*/
POINTER_SIZE_INT anti_fallback_size_in_mos;
float ratio_of_anti_fallback_size_to_nos = 0.25f;
anti_fallback_size_in_mos = (POINTER_SIZE_INT)(((float)total_free * ratio_of_anti_fallback_size_to_nos)/(1.0f + ratio_of_anti_fallback_size_to_nos + nos->survive_ratio));
if(anti_fallback_size_in_mos > DEFAULT_MOS_RESERVE_SIZE ){
/*If the computed anti_fallback_size_in_mos is too large, we reset it back to DEFAULT_MOS_RESERVE_SIZE .*/
anti_fallback_size_in_mos = DEFAULT_MOS_RESERVE_SIZE ;
/*Here, anti_fallback_size_in_mos must be smaller than TOTAL_FREE*/
nos_reserve_size = (POINTER_SIZE_INT)(((float)(total_free - anti_fallback_size_in_mos))/(1.0f + nos->survive_ratio));
}else{
nos_reserve_size = (POINTER_SIZE_INT)(((float)total_free)/(1.0f + ratio_of_anti_fallback_size_to_nos + nos->survive_ratio));
}
}else{
nos_reserve_size = total_free - MOS_RESERVE_SIZE;
}
/*NOS should not be zero, if there is only one block in non-los, i.e. in the former if sentence,
*if total_free = GC_BLOCK_SIZE_BYTES, then the computed nos_reserve_size is between zero
*and GC_BLOCK_SIZE_BYTES. In this case, we assign this block to NOS*/
if(nos_reserve_size <= GC_BLOCK_SIZE_BYTES) nos_reserve_size = GC_BLOCK_SIZE_BYTES;
#ifdef STATIC_NOS_MAPPING
if(nos_reserve_size > nos->reserved_heap_size) nos_reserve_size = nos->reserved_heap_size;
#endif
new_nos_size = round_down_to_size((POINTER_SIZE_INT)nos_reserve_size, GC_BLOCK_SIZE_BYTES);
if(gc->force_gen_mode){
new_nos_size = min_nos_size_bytes;
}
new_mos_size = total_size - new_nos_size;
#ifdef STATIC_NOS_MAPPING
if(new_mos_size > mos->reserved_heap_size) new_mos_size = mos->reserved_heap_size;
#endif
*nos_size = new_nos_size;
*mos_size = new_mos_size;
return TRUE;;
}
#ifndef STATIC_NOS_MAPPING
void gc_gen_adapt(GC_Gen* gc, int64 pause_time)
{
gc_decide_next_collect(gc, pause_time);
if(NOS_SIZE) return;
Blocked_Space* nos = (Blocked_Space*)gc->nos;
Blocked_Space* mos = (Blocked_Space*)gc->mos;
POINTER_SIZE_INT new_nos_size;
POINTER_SIZE_INT new_mos_size;
Boolean result = gc_compute_new_space_size(gc, &new_mos_size, &new_nos_size);
if(!result) return;
POINTER_SIZE_INT curr_nos_size = space_committed_size((Space*)nos);
//if( ABS_DIFF(new_nos_size, curr_nos_size) < NOS_COPY_RESERVE_DELTA )
if( new_nos_size == curr_nos_size ){
return;
}else if ( new_nos_size >= curr_nos_size ){
INFO2("gc.process", "GC: gc_gen space adjustment after GC["<<gc->num_collections<<"] ...");
POINTER_SIZE_INT adapt_size = new_nos_size - curr_nos_size;
INFO2("gc.space", "GC: Space Adapt: mos ---> nos ("
<<verbose_print_size(adapt_size)
<<" size was transferred from mos to nos)\n");
} else {
INFO2("gc.process", "GC: gc_gen space adjustment after GC["<<gc->num_collections<<"] ...");
POINTER_SIZE_INT adapt_size = curr_nos_size - new_nos_size;
INFO2("gc.space", "GC: Space Adapt: nos ---> mos ("
<<verbose_print_size(adapt_size)
<<" size was transferred from nos to mos)\n");
}
/* below are ajustment */
POINTER_SIZE_INT curr_heap_commit_end;
if(LOS_ADJUST_BOUNDARY)
curr_heap_commit_end = (POINTER_SIZE_INT)gc->heap_start + LOS_HEAD_RESERVE_FOR_HEAP_BASE + gc->committed_heap_size;
else
curr_heap_commit_end = (POINTER_SIZE_INT)nos->heap_start + nos->committed_heap_size;
void* new_nos_boundary = (void*)(curr_heap_commit_end - new_nos_size);
nos_boundary = nos_space_adjust((Space*)nos, new_nos_boundary, new_nos_size);
/* it's possible that nos can't accept the specified nos boundary, it gives a new one. */
assert(nos_boundary <= new_nos_boundary);
new_mos_size -= (POINTER_SIZE_INT)new_nos_boundary - (POINTER_SIZE_INT)nos_boundary;
blocked_space_adjust(mos, mos->heap_start, new_mos_size);
Block_Header* mos_last_block = (Block_Header*)&mos->blocks[mos->num_managed_blocks-1];
assert(mos->ceiling_block_idx == mos_last_block->block_idx);
Block_Header* nos_first_block = (Block_Header*)&nos->blocks[0];
/* this is redundant, because blocked_space_adjust doesn't set last block next to NULL.
mos_last_block->next = nos_first_block; */
if( gc_is_gen_mode())
HelperClass_set_NosBoundary(nos_boundary);
return;
}
#else /* i.e., #ifdef STATIC_NOS_MAPPING */
void gc_gen_adapt(GC_Gen* gc, int64 pause_time)
{
gc_decide_next_collect(gc, pause_time);
if(NOS_SIZE) return;
POINTER_SIZE_INT new_nos_size;
POINTER_SIZE_INT new_mos_size;
Boolean result = gc_compute_new_space_size(gc, &new_mos_size, &new_nos_size);
if(!result) return;
Blocked_Space* nos = (Blocked_Space*)gc->nos;
Blocked_Space* mos = (Blocked_Space*)gc->mos;
POINTER_SIZE_INT curr_nos_size = space_committed_size((Space*)nos);
//if( ABS_DIFF(new_nos_size, curr_nos_size) < NOS_COPY_RESERVE_DELTA )
if( new_nos_size == curr_nos_size ){
return;
}else if ( new_nos_size >= curr_nos_size ){
INFO2("gc.process", "GC: gc_gen space adjustment after GC["<<gc->num_collections<<"] ...\n");
POINTER_SIZE_INT adapt_size = new_nos_size - curr_nos_size;
INFO2("gc.space", "GC: Space Adapt: mos ---> nos ("
<<verbose_print_size(adapt_size)
<<" size was transferred from mos to nos)\n");
} else {
INFO2("gc.process", "GC: gc_gen space adjustment after GC["<<gc->num_collections<<"] ...\n");
POINTER_SIZE_INT adapt_size = curr_nos_size - new_nos_size;
INFO2("gc.space", "GC: Space Adapt: nos ---> mos ("
<<verbose_print_size(adapt_size)
<<" size was transferred from nos to mos)\n");
}
POINTER_SIZE_INT used_mos_size = blocked_space_used_mem_size((Blocked_Space*)mos);
POINTER_SIZE_INT free_mos_size = blocked_space_free_mem_size((Blocked_Space*)mos);
POINTER_SIZE_INT new_free_mos_size = new_mos_size - used_mos_size;
POINTER_SIZE_INT curr_mos_end = (POINTER_SIZE_INT)&mos->blocks[mos->free_block_idx - mos->first_block_idx];
POINTER_SIZE_INT mos_border = (POINTER_SIZE_INT)mos->heap_end;
if( curr_mos_end + new_free_mos_size > mos_border){
/* we can't let mos cross border */
new_free_mos_size = mos_border - curr_mos_end;
}
if(new_nos_size < curr_nos_size){
/* lets shrink nos */
assert(new_free_mos_size > free_mos_size);
blocked_space_shrink((Blocked_Space*)nos, curr_nos_size - new_nos_size);
blocked_space_extend((Blocked_Space*)mos, new_free_mos_size - free_mos_size);
}else if(new_nos_size > curr_nos_size){
/* lets grow nos */
assert(new_free_mos_size < free_mos_size);
blocked_space_shrink((Blocked_Space*)mos, free_mos_size - new_free_mos_size);
blocked_space_extend((Blocked_Space*)nos, new_nos_size - curr_nos_size);
}
Block_Header* mos_last_block = (Block_Header*)&mos->blocks[mos->num_managed_blocks-1];
Block_Header* nos_first_block = (Block_Header*)&nos->blocks[0];
mos_last_block->next = nos_first_block;
return;
}
#endif /* STATIC_NOS_MAPPING */