MemArena now slowly increases the size of each subsequent block.
This seems to work more nicely than a fixed block size, because it adapts to how much memory is being requested over-all. For example, a small scene won't allocate large amounts of RAM, but a large scene with large data won't be penalized with a lot of tiny allocations.
This commit is contained in:
parent
70f715ec2d
commit
db3e4ad129
|
@ -2,8 +2,9 @@ use std::slice;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::mem::{size_of, align_of};
|
use std::mem::{size_of, align_of};
|
||||||
|
|
||||||
const DEFAULT_BLOCK_SIZE: usize = (1 << 20) * 32; // 32 MiB
|
const GROWTH_FRACTION: usize = 8; // 1/N (smaller number leads to bigger allocations)
|
||||||
const DEFAULT_LARGE_ALLOCATION_THRESHOLD: usize = 1 << 20;
|
const DEFAULT_MIN_BLOCK_SIZE: usize = 1 << 10; // 1 KiB
|
||||||
|
const DEFAULT_MAX_WASTE_PERCENTAGE: usize = 10;
|
||||||
|
|
||||||
fn alignment_offset(addr: usize, alignment: usize) -> usize {
|
fn alignment_offset(addr: usize, alignment: usize) -> usize {
|
||||||
(alignment - (addr % alignment)) % alignment
|
(alignment - (addr % alignment)) % alignment
|
||||||
|
@ -11,50 +12,58 @@ fn alignment_offset(addr: usize, alignment: usize) -> usize {
|
||||||
|
|
||||||
/// A growable memory arena for Copy types.
|
/// A growable memory arena for Copy types.
|
||||||
///
|
///
|
||||||
/// The arena works by allocating memory in blocks of a fixed size. It doles
|
/// The arena works by allocating memory in blocks of slowly increasing size. It
|
||||||
/// out memory from the current block until an amount of memory is requested that
|
/// doles out memory from the current block until an amount of memory is requested
|
||||||
/// doesn't fit in the remainder of the current block, and then allocates a new
|
/// that doesn't fit in the remainder of the current block, and then allocates a new
|
||||||
/// block.
|
/// block.
|
||||||
///
|
///
|
||||||
/// Additionally, to minimize unused space in blocks, allocations above a specified
|
/// Additionally, it attempts to minimize wasted space through some heuristics. By
|
||||||
/// size (the large allocation threshold) are given their own block if they won't
|
/// default, it tries to keep memory waste within the arena below 10%.
|
||||||
/// fit in the remainder of the current block.
|
|
||||||
///
|
|
||||||
/// The block size and large allocation threshold are configurable.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MemArena {
|
pub struct MemArena {
|
||||||
blocks: RefCell<Vec<Vec<u8>>>,
|
blocks: RefCell<Vec<Vec<u8>>>,
|
||||||
block_size: usize,
|
min_block_size: usize,
|
||||||
large_alloc_threshold: usize,
|
max_waste_percentage: usize,
|
||||||
stat_space_occupied: Cell<usize>,
|
stat_space_occupied: Cell<usize>,
|
||||||
stat_space_allocated: Cell<usize>,
|
stat_space_allocated: Cell<usize>,
|
||||||
stat_large_blocks: Cell<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemArena {
|
impl MemArena {
|
||||||
/// Create a new arena, with default block size and large allocation threshold.
|
/// Create a new arena, with default minimum block size.
|
||||||
pub fn new() -> MemArena {
|
pub fn new() -> MemArena {
|
||||||
MemArena {
|
MemArena {
|
||||||
blocks: RefCell::new(vec![Vec::with_capacity(DEFAULT_BLOCK_SIZE)]),
|
blocks: RefCell::new(vec![Vec::with_capacity(DEFAULT_MIN_BLOCK_SIZE)]),
|
||||||
block_size: DEFAULT_BLOCK_SIZE,
|
min_block_size: DEFAULT_MIN_BLOCK_SIZE,
|
||||||
large_alloc_threshold: DEFAULT_LARGE_ALLOCATION_THRESHOLD,
|
max_waste_percentage: DEFAULT_MAX_WASTE_PERCENTAGE,
|
||||||
stat_space_occupied: Cell::new(DEFAULT_BLOCK_SIZE),
|
stat_space_occupied: Cell::new(DEFAULT_MIN_BLOCK_SIZE),
|
||||||
stat_space_allocated: Cell::new(0),
|
stat_space_allocated: Cell::new(0),
|
||||||
stat_large_blocks: Cell::new(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new arena, with a custom block size and large allocation threshold.
|
/// Create a new arena, with a specified minimum block size.
|
||||||
pub fn new_with_settings(block_size: usize, large_alloc_threshold: usize) -> MemArena {
|
pub fn with_min_block_size(min_block_size: usize) -> MemArena {
|
||||||
assert!(large_alloc_threshold <= block_size);
|
assert!(min_block_size > 0);
|
||||||
|
|
||||||
MemArena {
|
MemArena {
|
||||||
blocks: RefCell::new(vec![Vec::with_capacity(block_size)]),
|
blocks: RefCell::new(vec![Vec::with_capacity(min_block_size)]),
|
||||||
block_size: block_size,
|
min_block_size: min_block_size,
|
||||||
large_alloc_threshold: large_alloc_threshold,
|
max_waste_percentage: DEFAULT_MAX_WASTE_PERCENTAGE,
|
||||||
stat_space_occupied: Cell::new(block_size),
|
stat_space_occupied: Cell::new(min_block_size),
|
||||||
|
stat_space_allocated: Cell::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new arena, with a specified minimum block size and maximum waste percentage.
|
||||||
|
pub fn with_settings(min_block_size: usize, max_waste_percentage: usize) -> MemArena {
|
||||||
|
assert!(min_block_size > 0);
|
||||||
|
assert!(max_waste_percentage > 0 && max_waste_percentage <= 100);
|
||||||
|
|
||||||
|
MemArena {
|
||||||
|
blocks: RefCell::new(vec![Vec::with_capacity(min_block_size)]),
|
||||||
|
min_block_size: min_block_size,
|
||||||
|
max_waste_percentage: max_waste_percentage,
|
||||||
|
stat_space_occupied: Cell::new(min_block_size),
|
||||||
stat_space_allocated: Cell::new(0),
|
stat_space_allocated: Cell::new(0),
|
||||||
stat_large_blocks: Cell::new(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,17 +78,12 @@ impl MemArena {
|
||||||
/// allocation requests made to the arena by client code.
|
/// allocation requests made to the arena by client code.
|
||||||
///
|
///
|
||||||
/// Block count is the number of blocks that have been allocated.
|
/// Block count is the number of blocks that have been allocated.
|
||||||
///
|
pub fn stats(&self) -> (usize, usize, usize) {
|
||||||
/// Large block count is the number of blocks that have beem specifically
|
|
||||||
/// allocated for allocation requests that were above the large
|
|
||||||
/// allocation threshold.
|
|
||||||
pub fn stats(&self) -> (usize, usize, usize, usize) {
|
|
||||||
let occupied = self.stat_space_occupied.get();
|
let occupied = self.stat_space_occupied.get();
|
||||||
let allocated = self.stat_space_allocated.get();
|
let allocated = self.stat_space_allocated.get();
|
||||||
let blocks = self.blocks.borrow().len();
|
let blocks = self.blocks.borrow().len();
|
||||||
let large_blocks = self.stat_large_blocks.get();
|
|
||||||
|
|
||||||
(occupied, allocated, blocks, large_blocks)
|
(occupied, allocated, blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Frees all memory currently allocated by the arena, resetting itself to start
|
/// Frees all memory currently allocated by the arena, resetting itself to start
|
||||||
|
@ -92,11 +96,10 @@ impl MemArena {
|
||||||
|
|
||||||
blocks.clear();
|
blocks.clear();
|
||||||
blocks.shrink_to_fit();
|
blocks.shrink_to_fit();
|
||||||
blocks.push(Vec::with_capacity(self.block_size));
|
blocks.push(Vec::with_capacity(self.min_block_size));
|
||||||
|
|
||||||
self.stat_space_occupied.set(self.block_size);
|
self.stat_space_occupied.set(self.min_block_size);
|
||||||
self.stat_space_allocated.set(0);
|
self.stat_space_allocated.set(0);
|
||||||
self.stat_large_blocks.set(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates memory for and initializes a type T, returning a mutable reference to it.
|
/// Allocates memory for and initializes a type T, returning a mutable reference to it.
|
||||||
|
@ -159,6 +162,8 @@ impl MemArena {
|
||||||
|
|
||||||
/// Allocates space with a given size and alignment.
|
/// Allocates space with a given size and alignment.
|
||||||
///
|
///
|
||||||
|
/// This is the work-horse code of the MemArena.
|
||||||
|
///
|
||||||
/// CAUTION: this returns uninitialized memory. Make sure to initialize the
|
/// CAUTION: this returns uninitialized memory. Make sure to initialize the
|
||||||
/// memory after calling.
|
/// memory after calling.
|
||||||
unsafe fn alloc_raw(&self, size: usize, alignment: usize) -> *mut u8 {
|
unsafe fn alloc_raw(&self, size: usize, alignment: usize) -> *mut u8 {
|
||||||
|
@ -168,7 +173,7 @@ impl MemArena {
|
||||||
|
|
||||||
let mut blocks = self.blocks.borrow_mut();
|
let mut blocks = self.blocks.borrow_mut();
|
||||||
|
|
||||||
// If it's a zero-size allocation, just point to the beginning of the curent block.
|
// If it's a zero-size allocation, just point to the beginning of the current block.
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return blocks.first_mut().unwrap().as_mut_ptr();
|
return blocks.first_mut().unwrap().as_mut_ptr();
|
||||||
}
|
}
|
||||||
|
@ -189,12 +194,31 @@ impl MemArena {
|
||||||
}
|
}
|
||||||
// If it won't fit in the current block, create a new block and use that.
|
// If it won't fit in the current block, create a new block and use that.
|
||||||
else {
|
else {
|
||||||
|
let next_size = if blocks.len() >= GROWTH_FRACTION {
|
||||||
|
let a = self.stat_space_occupied.get() / GROWTH_FRACTION;
|
||||||
|
let b = a % self.min_block_size;
|
||||||
|
if b > 0 {
|
||||||
|
a - b + self.min_block_size
|
||||||
|
} else {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.min_block_size
|
||||||
|
};
|
||||||
|
|
||||||
|
let waste_percentage = {
|
||||||
|
let w1 = ((blocks[0].capacity() - blocks[0].len()) * 100) /
|
||||||
|
blocks[0].capacity();
|
||||||
|
let w2 = ((self.stat_space_occupied.get() - self.stat_space_allocated.get()) *
|
||||||
|
100) / self.stat_space_occupied.get();
|
||||||
|
if w1 < w2 { w1 } else { w2 }
|
||||||
|
};
|
||||||
|
|
||||||
// If it's a "large allocation", give it its own memory block.
|
// If it's a "large allocation", give it its own memory block.
|
||||||
if size > self.large_alloc_threshold {
|
if (size + alignment) > next_size || waste_percentage > self.max_waste_percentage {
|
||||||
// Update stats
|
// Update stats
|
||||||
self.stat_space_occupied
|
self.stat_space_occupied
|
||||||
.set(self.stat_space_occupied.get() + size + alignment - 1);
|
.set(self.stat_space_occupied.get() + size + alignment - 1);
|
||||||
self.stat_large_blocks.set(self.stat_large_blocks.get() + 1);
|
|
||||||
|
|
||||||
blocks.push(Vec::with_capacity(size + alignment - 1));
|
blocks.push(Vec::with_capacity(size + alignment - 1));
|
||||||
blocks.last_mut().unwrap().set_len(size + alignment - 1);
|
blocks.last_mut().unwrap().set_len(size + alignment - 1);
|
||||||
|
@ -208,9 +232,9 @@ impl MemArena {
|
||||||
// Otherwise create a new shared block.
|
// Otherwise create a new shared block.
|
||||||
else {
|
else {
|
||||||
// Update stats
|
// Update stats
|
||||||
self.stat_space_occupied.set(self.stat_space_occupied.get() + self.block_size);
|
self.stat_space_occupied.set(self.stat_space_occupied.get() + next_size);
|
||||||
|
|
||||||
blocks.push(Vec::with_capacity(self.block_size));
|
blocks.push(Vec::with_capacity(next_size));
|
||||||
let block_count = blocks.len();
|
let block_count = blocks.len();
|
||||||
blocks.swap(0, block_count - 1);
|
blocks.swap(0, block_count - 1);
|
||||||
|
|
||||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -138,7 +138,7 @@ fn main() {
|
||||||
if child.type_name() == "Scene" {
|
if child.type_name() == "Scene" {
|
||||||
println!("Building scene...");
|
println!("Building scene...");
|
||||||
|
|
||||||
let arena = MemArena::new_with_settings((1 << 20) * 64, (1 << 20) * 4);
|
let arena = MemArena::with_min_block_size((1 << 20) * 4);
|
||||||
let mut r = parse_scene(&arena, child).unwrap_or_else(|e| {
|
let mut r = parse_scene(&arena, child).unwrap_or_else(|e| {
|
||||||
e.print(&psy_contents);
|
e.print(&psy_contents);
|
||||||
panic!("Parse error.");
|
panic!("Parse error.");
|
||||||
|
@ -177,16 +177,27 @@ fn main() {
|
||||||
}
|
}
|
||||||
println!("\tWrote image in {:.3}s", t.tick());
|
println!("\tWrote image in {:.3}s", t.tick());
|
||||||
|
|
||||||
// Print memory stats if dev info is wanted.
|
// Print memory stats if stats are wanted.
|
||||||
if args.flag_stats {
|
if args.flag_stats {
|
||||||
let arena_stats = arena.stats();
|
let arena_stats = arena.stats();
|
||||||
|
let mib_occupied = arena_stats.0 as f64 / 1048576.0;
|
||||||
|
let mib_allocated = arena_stats.1 as f64 / 1048576.0;
|
||||||
|
|
||||||
println!("MemArena stats:");
|
println!("MemArena stats:");
|
||||||
println!("\tOccupied: {:.1} MiB",
|
|
||||||
arena_stats.0 as f64 / 1048576.0);
|
if mib_occupied >= 1.0 {
|
||||||
println!("\tAllocated: {:.1} MiB",
|
println!("\tOccupied: {:.1} MiB", mib_occupied);
|
||||||
arena_stats.1 as f64 / 1048576.0);
|
} else {
|
||||||
|
println!("\tOccupied: {:.4} MiB", mib_occupied);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mib_allocated >= 1.0 {
|
||||||
|
println!("\tUsed: {:.1} MiB", mib_allocated);
|
||||||
|
} else {
|
||||||
|
println!("\tUsed: {:.4} MiB", mib_allocated);
|
||||||
|
}
|
||||||
|
|
||||||
println!("\tTotal blocks: {}", arena_stats.2);
|
println!("\tTotal blocks: {}", arena_stats.2);
|
||||||
println!("\tLarge blocks: {}", arena_stats.3);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user