Skip to content

Commit 36f8c44

Browse files
authored
mmap adjacent chunks (#738)
When quarantining memory, call mmap once for adjacent chunks with the same state. This reduces the number of mmap calls and reduces start-up time.
1 parent 93cc820 commit 36f8c44

File tree

5 files changed

+544
-61
lines changed

5 files changed

+544
-61
lines changed

src/util/heap/layout/fragmented_mapper.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::mmapper::MapState;
22
use super::Mmapper;
3+
use crate::util::constants::BYTES_IN_PAGE;
34
use crate::util::conversions;
45
use crate::util::heap::layout::vm_layout_constants::*;
56
use crate::util::Address;
@@ -81,10 +82,19 @@ impl Mmapper for FragmentedMapper {
8182
}
8283

8384
fn quarantine_address_range(&self, mut start: Address, pages: usize) -> Result<()> {
85+
debug_assert!(start.is_aligned_to(BYTES_IN_PAGE));
86+
8487
let end = start + conversions::pages_to_bytes(pages);
88+
89+
// Each `MapState` entry governs a chunk.
90+
// Align down to the chunk start because we only mmap multiples of whole chunks.
91+
let mmap_start = conversions::mmap_chunk_align_down(start);
92+
93+
// We collect the chunk states from slabs to process them in bulk.
94+
let mut state_slices = vec![];
95+
8596
// Iterate over the slabs covered
8697
while start < end {
87-
let base = Self::slab_align_down(start);
8898
let high = if end > Self::slab_limit(start) && !Self::slab_limit(start).is_zero() {
8999
Self::slab_limit(start)
90100
} else {
@@ -96,19 +106,26 @@ impl Mmapper for FragmentedMapper {
96106
let end_chunk = Self::chunk_index(slab, conversions::mmap_chunk_align_up(high));
97107

98108
let mapped = self.get_or_allocate_slab_table(start);
109+
state_slices.push(&mapped[start_chunk..end_chunk]);
99110

100-
/* Iterate over the chunks within the slab */
101-
for (chunk, entry) in mapped.iter().enumerate().take(end_chunk).skip(start_chunk) {
102-
if matches!(entry.load(Ordering::Relaxed), MapState::Quarantined) {
103-
continue;
104-
}
105-
106-
let mmap_start = Self::chunk_index_to_address(base, chunk);
107-
let _guard = self.lock.lock().unwrap();
108-
MapState::transition_to_quarantined(entry, mmap_start).unwrap();
109-
}
110111
start = high;
111112
}
113+
114+
#[cfg(debug_assertions)]
115+
{
116+
// Check if the number of entries are normal.
117+
let mmap_end = conversions::mmap_chunk_align_up(end);
118+
let num_slices = state_slices.iter().map(|s| s.len()).sum::<usize>();
119+
120+
debug_assert_eq!(mmap_start + BYTES_IN_CHUNK * num_slices, mmap_end);
121+
}
122+
123+
// Transition the chunks in bulk.
124+
{
125+
let _guard = self.lock.lock().unwrap();
126+
MapState::bulk_transition_to_quarantined(state_slices.as_slice(), mmap_start)?;
127+
}
128+
112129
Ok(())
113130
}
114131

src/util/heap/layout/mmapper.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::util::heap::layout::vm_layout_constants::*;
22
use crate::util::memory::*;
3+
use crate::util::rust_util::rev_group::RevisitableGroupByForIterator;
34
use crate::util::Address;
45
use atomic::{Atomic, Ordering};
56
use std::io::Result;
@@ -129,6 +130,69 @@ impl MapState {
129130
res
130131
}
131132

133+
/// Equivalent to calling `transition_to_quarantined` on each element of `states`, but faster.
134+
/// The caller should hold a lock before invoking this method.
135+
///
136+
/// The memory region to transition starts from `mmap_start`. The size is the chunk size
137+
/// multiplied by the total number of `Atomic<MapState>` in the nested slice of slices.
138+
///
139+
/// This function is introduced to speed up initialization of MMTk. MMTk tries to quarantine
140+
/// very large amount of memory during start-up. If we quarantine one chunk at a time, it will
141+
/// require thousands of `mmap` calls to cover gigabytes of quarantined memory, introducing a
142+
/// noticeable delay.
143+
///
144+
/// Arguments:
145+
///
146+
/// * `state_slices`: A slice of slices. Each inner slice is a part of a `Slab`.
147+
/// * `mmap_start`: The start of the region to transition.
148+
pub(super) fn bulk_transition_to_quarantined(
149+
state_slices: &[&[Atomic<MapState>]],
150+
mmap_start: Address,
151+
) -> Result<()> {
152+
trace!(
153+
"Trying to bulk-quarantine {} - {}",
154+
mmap_start,
155+
mmap_start + MMAP_CHUNK_BYTES * state_slices.iter().map(|s| s.len()).sum::<usize>(),
156+
);
157+
158+
let mut start_index = 0;
159+
160+
for group in state_slices
161+
.iter()
162+
.copied()
163+
.flatten()
164+
.revisitable_group_by(|s| s.load(Ordering::Relaxed))
165+
{
166+
let end_index = start_index + group.len;
167+
let start_addr = mmap_start + MMAP_CHUNK_BYTES * start_index;
168+
let end_addr = mmap_start + MMAP_CHUNK_BYTES * end_index;
169+
170+
match group.key {
171+
MapState::Unmapped => {
172+
trace!("Trying to quarantine {} - {}", start_addr, end_addr);
173+
mmap_noreserve(start_addr, end_addr - start_addr)?;
174+
175+
for state in group {
176+
state.store(MapState::Quarantined, Ordering::Relaxed);
177+
}
178+
}
179+
MapState::Quarantined => {
180+
trace!("Already quarantine {} - {}", start_addr, end_addr);
181+
}
182+
MapState::Mapped => {
183+
trace!("Already mapped {} - {}", start_addr, end_addr);
184+
}
185+
MapState::Protected => {
186+
panic!("Cannot quarantine protected memory")
187+
}
188+
}
189+
190+
start_index = end_index;
191+
}
192+
193+
Ok(())
194+
}
195+
132196
/// Check the current MapState of the chunk, and transition the chunk to MapState::Protected.
133197
/// The caller should hold a lock before invoking this method.
134198
pub(super) fn transition_to_protected(

src/util/rust_util.rs renamed to src/util/rust_util/mod.rs

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
//! functionalities that we may expect the Rust programming language and its standard libraries
33
//! to provide.
44
5+
pub mod rev_group;
6+
pub mod zeroed_alloc;
7+
58
/// Const function for min value of two usize numbers.
69
pub const fn min_of_usize(a: usize, b: usize) -> usize {
710
if a > b {
@@ -127,53 +130,3 @@ mod initialize_once_tests {
127130
assert_eq!(INITIALIZE_COUNT.load(Ordering::SeqCst), 1);
128131
}
129132
}
130-
131-
/// This module is for allocating large arrays or vectors with initial zero values.
132-
///
133-
/// Note: The standard library uses the `IsZero` trait to specialize the intialization of `Vec<T>`
134-
/// if the initial element values are zero. Primitive type, such as `i8`, `usize`, `f32`, as well
135-
/// as types with known representations such as `Option<NonZeroUsize>` implement the `IsZero`
136-
/// trait. However, it has several limitations.
137-
///
138-
/// 1. Composite types, such as `SpaceDescriptor(usize)`, doesn't implement the `IsZero` trait,
139-
/// even if it has the `#[repr(transparent)]` annotation.
140-
/// 2. The `IsZero` trait is private to the `std` module, and we cannot use it.
141-
///
142-
/// Therefore, `vec![0usize; 33554432]` takes only 4 **microseconds**, while
143-
/// `vec![SpaceDescriptor(0); 33554432]` will take 22 **milliseconds** to execute on some machine.
144-
/// If such an allocation happens during start-up, the delay will be noticeable to light-weight
145-
/// scripting languages, such as Ruby.
146-
///
147-
/// We implement our own fast allocation of large zeroed vectors in this module. If one day Rust
148-
/// provides a standard way to optimize for zeroed allocation of vectors of composite types, we
149-
/// can switch to the standard mechanism.
150-
pub mod zeroed_alloc {
151-
152-
use std::alloc::{alloc_zeroed, Layout};
153-
154-
/// Allocate a `Vec<T>` of all-zero values.
155-
///
156-
/// This intends to be a faster alternative to `vec![T(0), size]`. It will allocate pre-zeroed
157-
/// buffer, and not store zero values to its elements as part of initialization.
158-
///
159-
/// It is useful when creating large (hundreds of megabytes) Vecs when the execution time is
160-
/// critical (such as during start-up, where a 100ms delay is obvious to small applications.)
161-
/// However, because of its unsafe nature, it should only be used when necessary.
162-
///
163-
/// Arguments:
164-
///
165-
/// - `T`: The element type.
166-
/// - `size`: The length and capacity of the created vector.
167-
///
168-
/// Returns the created vector.
169-
///
170-
/// # Unsafe
171-
///
172-
/// This function is unsafe. It will not call any constructor of `T`. The user must ensure
173-
/// that a value with all bits being zero is meaningful for type `T`.
174-
pub(crate) unsafe fn new_zeroed_vec<T>(size: usize) -> Vec<T> {
175-
let layout = Layout::array::<T>(size).unwrap();
176-
let ptr = alloc_zeroed(layout) as *mut T;
177-
Vec::from_raw_parts(ptr, size, size)
178-
}
179-
}

0 commit comments

Comments
 (0)