Skip to content

Commit c33c8b8

Browse files
authored
Add some more metrics to the pooling allocator (#11789)
* Add some more metrics to the pooling allocator This commit adds a few more metrics to the `PoolingAllocatorMetrics` type along the lines of accounting for more items as well as the unused slots in the pooling allocator. Notably the count of unused memory and table slots is exposed along with the number of bytes which are kept resident in these slots despite them not being in use. This involved a bit of plumbing to thread around the number of bytes that are actually kept resident to some more locations but is otherwise a pretty straightforward plumbing of accounting information we already had internally. * Fix configured build * Add some docs * Only run new test on Linux * Try to fix ASAN prtest:full * Shrink pooling size in tests
1 parent 8fbe4c2 commit c33c8b8

File tree

12 files changed

+505
-103
lines changed

12 files changed

+505
-103
lines changed

crates/wasmtime/src/runtime/vm/cow.rs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -514,21 +514,23 @@ impl MemoryImageSlot {
514514
/// argument is the maximum amount of memory to keep resident in this
515515
/// process's memory on Linux. Up to that much memory will be `memset` to
516516
/// zero where the rest of it will be reset or released with `madvise`.
517+
///
518+
/// Returns the number of bytes still resident in memory after this function
519+
/// has returned.
517520
#[allow(dead_code, reason = "only used in some cfgs")]
518521
pub(crate) fn clear_and_remain_ready(
519522
&mut self,
520523
pagemap: Option<&PageMap>,
521524
keep_resident: HostAlignedByteCount,
522525
decommit: impl FnMut(*mut u8, usize),
523-
) -> Result<()> {
526+
) -> Result<usize> {
524527
assert!(self.dirty);
525528

526-
unsafe {
527-
self.reset_all_memory_contents(pagemap, keep_resident, decommit)?;
528-
}
529+
let bytes_resident =
530+
unsafe { self.reset_all_memory_contents(pagemap, keep_resident, decommit)? };
529531

530532
self.dirty = false;
531-
Ok(())
533+
Ok(bytes_resident)
532534
}
533535

534536
#[allow(dead_code, reason = "only used in some cfgs")]
@@ -537,7 +539,7 @@ impl MemoryImageSlot {
537539
pagemap: Option<&PageMap>,
538540
keep_resident: HostAlignedByteCount,
539541
decommit: impl FnMut(*mut u8, usize),
540-
) -> Result<()> {
542+
) -> Result<usize> {
541543
match vm::decommit_behavior() {
542544
DecommitBehavior::Zero => {
543545
// If we're not on Linux then there's no generic platform way to
@@ -546,13 +548,13 @@ impl MemoryImageSlot {
546548
//
547549
// Additionally the previous image, if any, is dropped here
548550
// since it's no longer applicable to this mapping.
549-
self.reset_with_anon_memory()
551+
self.reset_with_anon_memory()?;
552+
Ok(0)
550553
}
551554
DecommitBehavior::RestoreOriginalMapping => {
552-
unsafe {
553-
self.reset_with_original_mapping(pagemap, keep_resident, decommit);
554-
}
555-
Ok(())
555+
let bytes_resident =
556+
unsafe { self.reset_with_original_mapping(pagemap, keep_resident, decommit) };
557+
Ok(bytes_resident)
556558
}
557559
}
558560
}
@@ -563,29 +565,25 @@ impl MemoryImageSlot {
563565
pagemap: Option<&PageMap>,
564566
keep_resident: HostAlignedByteCount,
565567
decommit: impl FnMut(*mut u8, usize),
566-
) {
568+
) -> usize {
567569
assert_eq!(
568570
vm::decommit_behavior(),
569571
DecommitBehavior::RestoreOriginalMapping
570572
);
571573

572574
unsafe {
573-
match &self.image {
575+
return match &self.image {
574576
// If there's a backing image then manually resetting a region
575577
// is a bit trickier than without an image, so delegate to the
576578
// helper function below.
577-
Some(image) => {
578-
reset_with_pagemap(
579-
pagemap,
580-
self.base.as_mut_ptr(),
581-
self.accessible,
582-
keep_resident,
583-
|region| {
584-
manually_reset_region(self.base.as_mut_ptr().addr(), image, region)
585-
},
586-
decommit,
587-
);
588-
}
579+
Some(image) => reset_with_pagemap(
580+
pagemap,
581+
self.base.as_mut_ptr(),
582+
self.accessible,
583+
keep_resident,
584+
|region| manually_reset_region(self.base.as_mut_ptr().addr(), image, region),
585+
decommit,
586+
),
589587

590588
// If there's no memory image for this slot then pages are always
591589
// manually reset back to zero or given to `decommit`.
@@ -597,7 +595,7 @@ impl MemoryImageSlot {
597595
|region| region.fill(0),
598596
decommit,
599597
),
600-
}
598+
};
601599
}
602600

603601
/// Manually resets `region` back to its original contents as specified

crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ pub struct PoolingInstanceAllocator {
318318

319319
#[cfg(feature = "gc")]
320320
gc_heaps: GcHeapPool,
321+
#[cfg(feature = "gc")]
322+
live_gc_heaps: AtomicUsize,
321323

322324
#[cfg(feature = "async")]
323325
stacks: StackPool,
326+
#[cfg(feature = "async")]
327+
live_stacks: AtomicUsize,
324328

325329
pagemap: Option<PageMap>,
326330
}
@@ -350,10 +354,16 @@ impl Drop for PoolingInstanceAllocator {
350354
debug_assert!(self.tables.is_empty());
351355

352356
#[cfg(feature = "gc")]
353-
debug_assert!(self.gc_heaps.is_empty());
357+
{
358+
debug_assert!(self.gc_heaps.is_empty());
359+
debug_assert_eq!(self.live_gc_heaps.load(Ordering::Acquire), 0);
360+
}
354361

355362
#[cfg(feature = "async")]
356-
debug_assert!(self.stacks.is_empty());
363+
{
364+
debug_assert!(self.stacks.is_empty());
365+
debug_assert_eq!(self.live_stacks.load(Ordering::Acquire), 0);
366+
}
357367
}
358368
}
359369

@@ -372,8 +382,12 @@ impl PoolingInstanceAllocator {
372382
live_tables: AtomicUsize::new(0),
373383
#[cfg(feature = "gc")]
374384
gc_heaps: GcHeapPool::new(config)?,
385+
#[cfg(feature = "gc")]
386+
live_gc_heaps: AtomicUsize::new(0),
375387
#[cfg(feature = "async")]
376388
stacks: StackPool::new(config)?,
389+
#[cfg(feature = "async")]
390+
live_stacks: AtomicUsize::new(0),
377391
pagemap: match config.pagemap_scan {
378392
Enabled::Auto => PageMap::new(),
379393
Enabled::Yes => Some(PageMap::new().ok_or_else(|| {
@@ -704,7 +718,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
704718
// reservation.
705719
let mut image = memory.unwrap_static_image();
706720
let mut queue = DecommitQueue::default();
707-
image
721+
let bytes_resident = image
708722
.clear_and_remain_ready(
709723
self.pagemap.as_ref(),
710724
self.memories.keep_resident,
@@ -722,7 +736,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
722736
// SAFETY: this image is not in use and its memory regions were enqueued
723737
// with `push_raw` above.
724738
unsafe {
725-
queue.push_memory(allocation_index, image);
739+
queue.push_memory(allocation_index, image, bytes_resident);
726740
}
727741
self.merge_or_flush(queue);
728742
}
@@ -770,42 +784,45 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
770784
// method is called and additionally all image ranges are pushed with
771785
// the understanding that the memory won't get used until the whole
772786
// queue is flushed.
773-
unsafe {
787+
let bytes_resident = unsafe {
774788
self.tables.reset_table_pages_to_zero(
775789
self.pagemap.as_ref(),
776790
allocation_index,
777791
&mut table,
778792
|ptr, len| {
779793
queue.push_raw(ptr, len);
780794
},
781-
);
782-
}
795+
)
796+
};
783797

784798
// SAFETY: the table has had all its memory regions enqueued above.
785799
unsafe {
786-
queue.push_table(allocation_index, table);
800+
queue.push_table(allocation_index, table, bytes_resident);
787801
}
788802
self.merge_or_flush(queue);
789803
}
790804

791805
#[cfg(feature = "async")]
792806
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
793-
self.with_flush_and_retry(|| self.stacks.allocate())
807+
let ret = self.with_flush_and_retry(|| self.stacks.allocate())?;
808+
self.live_stacks.fetch_add(1, Ordering::Relaxed);
809+
Ok(ret)
794810
}
795811

796812
#[cfg(feature = "async")]
797813
unsafe fn deallocate_fiber_stack(&self, mut stack: wasmtime_fiber::FiberStack) {
814+
self.live_stacks.fetch_sub(1, Ordering::Relaxed);
798815
let mut queue = DecommitQueue::default();
799816
// SAFETY: the stack is no longer in use by definition when this
800817
// function is called and memory ranges pushed here are otherwise no
801818
// longer in use.
802-
unsafe {
819+
let bytes_resident = unsafe {
803820
self.stacks
804-
.zero_stack(&mut stack, |ptr, len| queue.push_raw(ptr, len));
805-
}
821+
.zero_stack(&mut stack, |ptr, len| queue.push_raw(ptr, len))
822+
};
806823
// SAFETY: this stack's memory regions were enqueued above.
807824
unsafe {
808-
queue.push_stack(stack);
825+
queue.push_stack(stack, bytes_resident);
809826
}
810827
self.merge_or_flush(queue);
811828
}
@@ -834,8 +851,11 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
834851
memory_alloc_index: MemoryAllocationIndex,
835852
memory: Memory,
836853
) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
837-
self.gc_heaps
838-
.allocate(engine, gc_runtime, memory_alloc_index, memory)
854+
let ret = self
855+
.gc_heaps
856+
.allocate(engine, gc_runtime, memory_alloc_index, memory)?;
857+
self.live_gc_heaps.fetch_add(1, Ordering::Relaxed);
858+
Ok(ret)
839859
}
840860

841861
#[cfg(feature = "gc")]
@@ -844,6 +864,7 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
844864
allocation_index: GcHeapAllocationIndex,
845865
gc_heap: Box<dyn GcHeap>,
846866
) -> (MemoryAllocationIndex, Memory) {
867+
self.live_gc_heaps.fetch_sub(1, Ordering::Relaxed);
847868
self.gc_heaps.deallocate(allocation_index, gc_heap)
848869
}
849870

crates/wasmtime/src/runtime/vm/instance/allocator/pooling/decommit_queue.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ unsafe impl Sync for SendSyncStack {}
5151
#[derive(Default)]
5252
pub struct DecommitQueue {
5353
raw: SmallVec<[IoVec; 2]>,
54-
memories: SmallVec<[(MemoryAllocationIndex, MemoryImageSlot); 1]>,
55-
tables: SmallVec<[(TableAllocationIndex, Table); 1]>,
54+
memories: SmallVec<[(MemoryAllocationIndex, MemoryImageSlot, usize); 1]>,
55+
tables: SmallVec<[(TableAllocationIndex, Table, usize); 1]>,
5656
#[cfg(feature = "async")]
57-
stacks: SmallVec<[SendSyncStack; 1]>,
57+
stacks: SmallVec<[(SendSyncStack, usize); 1]>,
5858
//
5959
// TODO: GC heaps are not well-integrated with the pooling allocator
6060
// yet. Once we better integrate them, we should start (optionally) zeroing
@@ -123,8 +123,10 @@ impl DecommitQueue {
123123
&mut self,
124124
allocation_index: MemoryAllocationIndex,
125125
image: MemoryImageSlot,
126+
bytes_resident: usize,
126127
) {
127-
self.memories.push((allocation_index, image));
128+
self.memories
129+
.push((allocation_index, image, bytes_resident));
128130
}
129131

130132
/// Push a table into the queue.
@@ -133,8 +135,13 @@ impl DecommitQueue {
133135
///
134136
/// This table should not be in use, and its decommit regions must have
135137
/// already been enqueued via `self.enqueue_raw`.
136-
pub unsafe fn push_table(&mut self, allocation_index: TableAllocationIndex, table: Table) {
137-
self.tables.push((allocation_index, table));
138+
pub unsafe fn push_table(
139+
&mut self,
140+
allocation_index: TableAllocationIndex,
141+
table: Table,
142+
bytes_resident: usize,
143+
) {
144+
self.tables.push((allocation_index, table, bytes_resident));
138145
}
139146

140147
/// Push a stack into the queue.
@@ -144,8 +151,8 @@ impl DecommitQueue {
144151
/// This stack should not be in use, and its decommit regions must have
145152
/// already been enqueued via `self.enqueue_raw`.
146153
#[cfg(feature = "async")]
147-
pub unsafe fn push_stack(&mut self, stack: FiberStack) {
148-
self.stacks.push(SendSyncStack(stack));
154+
pub unsafe fn push_stack(&mut self, stack: FiberStack, bytes_resident: usize) {
155+
self.stacks.push((SendSyncStack(stack), bytes_resident));
149156
}
150157

151158
fn decommit_all_raw(&mut self) {
@@ -174,23 +181,25 @@ impl DecommitQueue {
174181
// lists. This is safe, and they are ready for reuse, now that their
175182
// memory regions have been decommitted.
176183
let mut deallocated_any = false;
177-
for (allocation_index, image) in self.memories {
184+
for (allocation_index, image, bytes_resident) in self.memories {
178185
deallocated_any = true;
179186
unsafe {
180-
pool.memories.deallocate(allocation_index, image);
187+
pool.memories
188+
.deallocate(allocation_index, image, bytes_resident);
181189
}
182190
}
183-
for (allocation_index, table) in self.tables {
191+
for (allocation_index, table, bytes_resident) in self.tables {
184192
deallocated_any = true;
185193
unsafe {
186-
pool.tables.deallocate(allocation_index, table);
194+
pool.tables
195+
.deallocate(allocation_index, table, bytes_resident);
187196
}
188197
}
189198
#[cfg(feature = "async")]
190-
for stack in self.stacks {
199+
for (stack, bytes_resident) in self.stacks {
191200
deallocated_any = true;
192201
unsafe {
193-
pool.stacks.deallocate(stack.0);
202+
pool.stacks.deallocate(stack.0, bytes_resident);
194203
}
195204
}
196205

crates/wasmtime/src/runtime/vm/instance/allocator/pooling/gc_heap_pool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl GcHeapPool {
133133
heaps[allocation_index.index()].dealloc(heap)
134134
};
135135

136-
self.index_allocator.free(SlotId(allocation_index.0));
136+
self.index_allocator.free(SlotId(allocation_index.0), 0);
137137

138138
(memory_alloc_index, memory)
139139
}

crates/wasmtime/src/runtime/vm/instance/allocator/pooling/generic_stack_pool.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ pub struct StackPool {
2828
}
2929

3030
impl StackPool {
31+
#[cfg(test)]
32+
pub fn enabled() -> bool {
33+
false
34+
}
35+
3136
pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
3237
Ok(StackPool {
3338
stack_size: config.stack_size,
@@ -69,15 +74,24 @@ impl StackPool {
6974
&self,
7075
_stack: &mut wasmtime_fiber::FiberStack,
7176
_decommit: impl FnMut(*mut u8, usize),
72-
) {
77+
) -> usize {
7378
// No need to actually zero the stack, since the stack won't ever be
7479
// reused on non-unix systems.
80+
0
7581
}
7682

7783
/// Safety: see the unix implementation.
78-
pub unsafe fn deallocate(&self, stack: wasmtime_fiber::FiberStack) {
84+
pub unsafe fn deallocate(&self, stack: wasmtime_fiber::FiberStack, _bytes_resident: usize) {
7985
self.live_stacks.fetch_sub(1, Ordering::AcqRel);
8086
// A no-op as we don't actually own the fiber stack on Windows.
8187
let _ = stack;
8288
}
89+
90+
pub fn unused_warm_slots(&self) -> u32 {
91+
0
92+
}
93+
94+
pub fn unused_bytes_resident(&self) -> Option<usize> {
95+
None
96+
}
8397
}

0 commit comments

Comments
 (0)