Rework storage and use of GC statistics

This commit makes two main changes:

1. Various configuration statistics that were duplicated in the
   GenerationConfig type have been removed, in favour of using the
   source values from the Config type directly.

2. Updating garbage collection statistics has been reworked, to prevent
   garbage collection from running too frequently.

The first change results in processes requiring less memory, though we
are talking about only a handful of bytes.

The second change is more interesting. Prior to this commit, the number
of allocated blocks was reset to the number of live blocks. If this
number would be great enough, the next garbage collection cycle could be
scheduled much sooner than necessary.

To illustrate this, let's say our threshold is 10 Immix blocks. For the
first garbage collection to occur, we thus need to allocate 10 Immix
blocks. If 5 Immix blocks remain live after a cycle, we would only need
to allocate 5 Immix blocks to trigger the next cycle; instead of
allocating 10 Immix blocks. This commit fixes this by resetting the
number of allocated blocks after every cycle.
parent 0a762c81
......@@ -8,6 +8,7 @@
//! of the VM to easily access these configuration details.
#![cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
use immix::block::BLOCK_SIZE;
use num_cpus;
use std::env;
use std::path::PathBuf;
......@@ -23,6 +24,14 @@ macro_rules! set_from_env {
}};
}
const DEFAULT_YOUNG_THRESHOLD: usize = (8 * 1024 * 1024) / BLOCK_SIZE;
const DEFAULT_MATURE_THRESHOLD: usize = (16 * 1024 * 1024) / BLOCK_SIZE;
const DEFAULT_MAILBOX_THRESHOLD: usize = 1;
const DEFAULT_GROWTH_FACTOR: f64 = 1.5;
const DEFAULT_GROWTH_THRESHOLD: f64 = 0.9;
const DEFAULT_SUSPENSION_CHECK_INTERVAL: f64 = 0.1;
const DEFAULT_REDUCTIONS: usize = 1000;
/// Structure containing the configuration settings for the virtual machine.
pub struct Config {
/// The directories to search in for extra bytecode files to run.
......@@ -52,31 +61,31 @@ pub struct Config {
/// The number of seconds to wait between checking for suspended processes.
pub suspension_check_interval: f64,
/// The amount of memory that can be allocated in the young generation
/// before triggering a young collection.
/// The number of memory blocks that can be allocated before triggering a
/// young collection.
pub young_threshold: usize,
/// The amount of memory that can be allocated in the mature generation
/// before triggering a full collection.
/// The number of memory blocks that can be allocated before triggering a
/// mature collection.
pub mature_threshold: usize,
/// The block allocation growth factor for the heap.
pub heap_growth_factor: f32,
pub heap_growth_factor: f64,
/// The percentage of memory in the heap (relative to its threshold) that
/// should be used before increasing the heap size.
pub heap_growth_threshold: f32,
pub heap_growth_threshold: f64,
/// The amount of memory that can be allocated for a mailbox before
/// triggering a mailbox collection.
/// The number of memory blocks that can be allocated before triggering a
/// mailbox collection.
pub mailbox_threshold: usize,
/// The block allocation growth factor for the mailbox heap.
pub mailbox_growth_factor: f32,
pub mailbox_growth_factor: f64,
/// The percentage of memory in the mailbox heap that should be used before
/// increasing the size.
pub mailbox_growth_threshold: f32,
pub mailbox_growth_threshold: f64,
}
impl Config {
......@@ -90,15 +99,15 @@ impl Config {
finalizer_threads: cpu_count,
secondary_threads: cpu_count,
generic_parallel_threads: cpu_count,
reductions: 1000,
suspension_check_interval: 0.1,
young_threshold: 8 * 1024 * 1024,
mature_threshold: 16 * 1024 * 1024,
heap_growth_factor: 1.5,
heap_growth_threshold: 0.9,
mailbox_threshold: 32 * 1024,
mailbox_growth_factor: 1.5,
mailbox_growth_threshold: 0.9,
reductions: DEFAULT_REDUCTIONS,
suspension_check_interval: DEFAULT_SUSPENSION_CHECK_INTERVAL,
young_threshold: DEFAULT_YOUNG_THRESHOLD,
mature_threshold: DEFAULT_MATURE_THRESHOLD,
heap_growth_factor: DEFAULT_GROWTH_FACTOR,
heap_growth_threshold: DEFAULT_GROWTH_THRESHOLD,
mailbox_threshold: DEFAULT_MAILBOX_THRESHOLD,
mailbox_growth_factor: DEFAULT_GROWTH_FACTOR,
mailbox_growth_threshold: DEFAULT_GROWTH_THRESHOLD,
}
}
......@@ -121,13 +130,13 @@ impl Config {
set_from_env!(self, young_threshold, "YOUNG_THRESHOLD", usize);
set_from_env!(self, mature_threshold, "MATURE_THRESHOLD", usize);
set_from_env!(self, heap_growth_factor, "HEAP_GROWTH_FACTOR", f32);
set_from_env!(self, heap_growth_factor, "HEAP_GROWTH_FACTOR", f64);
set_from_env!(
self,
heap_growth_threshold,
"HEAP_GROWTH_THRESHOLD",
f32
f64
);
set_from_env!(self, mailbox_threshold, "MAILBOX_THRESHOLD", usize);
......@@ -136,14 +145,14 @@ impl Config {
self,
mailbox_growth_factor,
"MAILBOX_GROWTH_FACTOR",
f32
f64
);
set_from_env!(
self,
mailbox_growth_threshold,
"MAILBOX_GROWTH_THRESHOLD",
f32
f64
);
}
......
......@@ -24,7 +24,7 @@ pub fn collect(vm_state: &RcState, process: &RcProcess, profile: &mut Profile) {
profile.reclaim.start();
process.reclaim_blocks(vm_state, collect_mature);
process.update_collection_statistics(collect_mature);
process.update_collection_statistics(&vm_state.config, collect_mature);
profile.reclaim.stop();
......
......@@ -24,7 +24,7 @@ pub fn collect(vm_state: &RcState, process: &RcProcess, profile: &mut Profile) {
profile.reclaim.start();
mailbox.allocator.reclaim_blocks(vm_state);
process.update_mailbox_collection_statistics();
process.update_mailbox_collection_statistics(&vm_state.config);
drop(lock); // unlock as soon as possible
......
//! Configuration for heap generations.
use immix::block::BLOCK_SIZE;
pub struct GenerationConfig {
/// The maximum number of blocks that can be allocated before triggering a
/// garbage collection.
pub threshold: usize,
/// The number of blocks that have been allocated.
/// The number of blocks that have been allocated since the last garbage
/// collection.
pub block_allocations: usize,
/// The percentage of blocks that should be used (relative to the threshold)
/// before incrementing the threshold.
pub growth_threshold: f32,
/// The factor to grow the allocation threshold by.
pub growth_factor: f32,
/// Boolean indicating if this generation should be collected.
pub collect: bool,
}
impl GenerationConfig {
pub fn new(bytes: usize, percentage: f32, growth_factor: f32) -> Self {
pub fn new(threshold: usize) -> Self {
GenerationConfig {
threshold: bytes / BLOCK_SIZE,
threshold,
block_allocations: 0,
collect: false,
growth_threshold: percentage,
growth_factor,
}
}
pub fn should_increment(&self) -> bool {
let percentage = self.block_allocations as f32 / self.threshold as f32;
self.allocation_threshold_exceeded()
|| percentage >= self.growth_threshold
/// Returns true if the allocation threshold should be increased.
///
/// The `blocks` argument should specify the current number of live blocks.
pub fn should_increase_threshold(
&self,
blocks: usize,
growth_threshold: f64,
) -> bool {
let percentage = blocks as f64 / self.threshold as f64;
percentage >= growth_threshold
}
pub fn increment_threshold(&mut self) {
pub fn increment_threshold(&mut self, growth_factor: f64) {
self.threshold =
(self.threshold as f32 * self.growth_factor).ceil() as usize;
(self.threshold as f64 * growth_factor).ceil() as usize;
}
pub fn allocation_threshold_exceeded(&self) -> bool {
......@@ -49,10 +42,6 @@ impl GenerationConfig {
pub fn increment_allocations(&mut self) {
self.block_allocations += 1;
if self.allocation_threshold_exceeded() && !self.collect {
self.collect = true;
}
}
}
......@@ -61,41 +50,37 @@ mod tests {
use super::*;
#[test]
fn test_should_increment_with_too_many_blocks() {
let mut config = GenerationConfig::new(BLOCK_SIZE, 0.9, 2.0);
assert_eq!(config.should_increment(), false);
fn test_should_increase_threshold_with_too_many_blocks() {
let config = GenerationConfig::new(1);
config.block_allocations = 10;
assert_eq!(config.should_increase_threshold(0, 0.9), false);
assert!(config.should_increment());
assert!(config.should_increase_threshold(10, 0.9));
}
#[test]
fn test_should_increment_with_large_usage_percentage() {
let mut config = GenerationConfig::new(BLOCK_SIZE * 10, 0.9, 2.0);
fn test_should_increase_threshold_with_large_usage_percentage() {
let config = GenerationConfig::new(10);
assert_eq!(config.should_increment(), false);
assert_eq!(config.should_increase_threshold(1, 0.9), false);
config.block_allocations = 9;
assert!(config.should_increment());
assert!(config.should_increase_threshold(9, 0.9));
}
#[test]
fn test_increment_threshold() {
let mut config = GenerationConfig::new(BLOCK_SIZE, 0.9, 2.0);
let mut config = GenerationConfig::new(1);
assert_eq!(config.threshold, 1);
config.increment_threshold();
config.increment_threshold(2.0);
assert_eq!(config.threshold, 2);
}
#[test]
fn test_allocation_threshold_exceeded() {
let mut config = GenerationConfig::new(BLOCK_SIZE, 0.9, 2.0);
let mut config = GenerationConfig::new(1);
assert_eq!(config.allocation_threshold_exceeded(), false);
......@@ -106,11 +91,11 @@ mod tests {
#[test]
fn test_increment_allocations() {
let mut config = GenerationConfig::new(BLOCK_SIZE, 0.9, 2.0);
let mut config = GenerationConfig::new(1);
config.increment_allocations();
assert_eq!(config.block_allocations, 1);
assert!(config.collect);
assert!(config.allocation_threshold_exceeded());
}
}
......@@ -51,18 +51,6 @@ impl LocalAllocator {
global_allocator: RcGlobalAllocator,
config: &Config,
) -> LocalAllocator {
let young_config = GenerationConfig::new(
config.young_threshold,
config.heap_growth_threshold,
config.heap_growth_factor,
);
let mature_config = GenerationConfig::new(
config.mature_threshold,
config.heap_growth_threshold,
config.heap_growth_factor,
);
LocalAllocator {
global_allocator,
young_generation: [
......@@ -73,8 +61,8 @@ impl LocalAllocator {
],
eden_index: 0,
mature_generation: Bucket::with_age(MATURE),
young_config,
mature_config,
young_config: GenerationConfig::new(config.young_threshold),
mature_config: GenerationConfig::new(config.mature_threshold),
remembered_set: HashSet::new(),
}
}
......@@ -92,11 +80,11 @@ impl LocalAllocator {
}
pub fn should_collect_young(&self) -> bool {
self.young_config.collect
self.young_config.allocation_threshold_exceeded()
}
pub fn should_collect_mature(&self) -> bool {
self.mature_config.collect
self.mature_config.allocation_threshold_exceeded()
}
/// Prepares for a garbage collection.
......@@ -201,35 +189,42 @@ impl LocalAllocator {
}
}
pub fn update_block_allocations(&mut self) {
self.young_config.block_allocations = self
.young_generation
.iter()
.map(|bucket| bucket.number_of_blocks())
.sum();
pub fn update_collection_statistics(&mut self, config: &Config) {
self.increment_young_ages();
self.mature_config.block_allocations =
self.mature_generation.number_of_blocks();
}
self.young_config.block_allocations = 0;
self.mature_config.block_allocations = 0;
pub fn update_collection_statistics(&mut self) {
self.young_config.collect = false;
self.mature_config.collect = false;
let young_blocks = self.number_of_young_blocks();
let mature_blocks = self.mature_generation.number_of_blocks();
self.increment_young_ages();
self.update_block_allocations();
let threshold = config.heap_growth_threshold;
let factor = config.heap_growth_factor;
if self.mature_config.should_increment() {
if self
.mature_config
.should_increase_threshold(mature_blocks, threshold)
{
// If the mature generation is running full we also want
// to increase the young generation to reduce the number of
// objects that are promoted prematurely.
self.young_config.increment_threshold();
self.mature_config.increment_threshold();
} else if self.young_config.should_increment() {
self.young_config.increment_threshold();
self.young_config.increment_threshold(factor);
self.mature_config.increment_threshold(factor);
} else if self
.young_config
.should_increase_threshold(young_blocks, threshold)
{
self.young_config.increment_threshold(factor);
}
}
pub fn number_of_young_blocks(&self) -> usize {
self.young_generation
.iter()
.map(|bucket| bucket.number_of_blocks())
.sum()
}
pub fn has_remembered_objects(&self) -> bool {
!self.remembered_set.is_empty()
}
......
......@@ -25,16 +25,10 @@ pub struct MailboxAllocator {
impl MailboxAllocator {
pub fn new(global_allocator: RcGlobalAllocator, config: &Config) -> Self {
let config = GenerationConfig::new(
config.mailbox_threshold,
config.mailbox_growth_threshold,
config.mailbox_growth_factor,
);
MailboxAllocator {
global_allocator,
bucket: Bucket::with_age(MAILBOX),
config,
config: GenerationConfig::new(config.mailbox_threshold),
}
}
......@@ -63,19 +57,19 @@ impl MailboxAllocator {
}
pub fn should_collect(&self) -> bool {
self.config.collect
self.config.allocation_threshold_exceeded()
}
pub fn update_block_allocations(&mut self) {
self.config.block_allocations = self.bucket.number_of_blocks();
}
pub fn update_collection_statistics(&mut self, config: &Config) {
self.config.block_allocations = 0;
pub fn update_collection_statistics(&mut self) {
self.config.collect = false;
self.update_block_allocations();
let blocks = self.bucket.number_of_blocks();
if self.config.should_increment() {
self.config.increment_threshold();
if self
.config
.should_increase_threshold(blocks, config.heap_growth_threshold)
{
self.config.increment_threshold(config.heap_growth_factor);
}
}
}
......
......@@ -529,10 +529,10 @@ impl Process {
state.global_allocator.add_blocks(&mut blocks);
}
pub fn update_collection_statistics(&self, mature: bool) {
pub fn update_collection_statistics(&self, config: &Config, mature: bool) {
let local_data = self.local_data_mut();
local_data.allocator.update_collection_statistics();
local_data.allocator.update_collection_statistics(config);
if mature {
local_data.mature_collections += 1;
......@@ -541,11 +541,14 @@ impl Process {
}
}
pub fn update_mailbox_collection_statistics(&self) {
pub fn update_mailbox_collection_statistics(&self, config: &Config) {
let local_data = self.local_data_mut();
local_data.mailbox_collections += 1;
local_data.mailbox.allocator.update_collection_statistics();
local_data
.mailbox
.allocator
.update_collection_statistics(config);
}
pub fn is_main(&self) -> bool {
......@@ -611,9 +614,9 @@ mod tests {
#[test]
fn test_update_collection_statistics_without_mature() {
let (_machine, _block, process) = setup();
let (machine, _block, process) = setup();
process.update_collection_statistics(false);
process.update_collection_statistics(&machine.state.config, false);
let local_data = process.local_data();
......@@ -622,9 +625,9 @@ mod tests {
#[test]
fn test_update_collection_statistics_with_mature() {
let (_machine, _block, process) = setup();
let (machine, _block, process) = setup();
process.update_collection_statistics(true);
process.update_collection_statistics(&machine.state.config, true);
let local_data = process.local_data();
......@@ -634,9 +637,9 @@ mod tests {
#[test]
fn test_update_mailbox_collection_statistics() {
let (_machine, _block, process) = setup();
let (machine, _block, process) = setup();
process.update_mailbox_collection_statistics();
process.update_mailbox_collection_statistics(&machine.state.config);
let local_data = process.local_data();
......@@ -743,7 +746,7 @@ mod tests {
// This test is put in place to ensure the type size doesn't change
// unintentionally.
assert_eq!(size, 696);
assert_eq!(size, 648);
}
#[test]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment