From 17287dd5cb41e73911949e03f9ef20f145b78e5e Mon Sep 17 00:00:00 2001 From: Maksym Arutyunyan Date: Wed, 20 Aug 2025 13:39:28 +0200 Subject: [PATCH 1/4] docs: update example in the docs --- README.md | 12 ++++++++++-- docs/src/concepts/memory-manager.md | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b5af4899..35172703 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,11 @@ Consider this migration scenario: ```rust use ic_stable_structures::{ memory_manager::{MemoryId, MemoryManager}, - BTreeMap, DefaultMemoryImpl, + BTreeMap, DefaultMemoryImpl, Memory, }; -let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); +let mem = DefaultMemoryImpl::default(); +let mem_mgr = MemoryManager::init(mem.clone()); let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== @@ -122,10 +123,13 @@ map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a +let size_before_migration = mem.size(); let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B allocates NEW memory +let size_after_migration = mem.size(); // Result: 2x memory usage +assert!(size_before_migration < size_after_migration); // ======================================== // Scenario 2: WITH reclamation @@ -133,12 +137,16 @@ map_b.insert(1, data.unwrap()); // B allocates NEW memory let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration +map_a.clear_new(); // A is now empty drop(map_a); // Drop A completely +let size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets +let size_after_migration = mem.size(); // Result: 1x memory usage +assert!(size_before_migration == size_after_migration); ``` **Important**: Always drop the original structure before calling `reclaim_memory`. diff --git a/docs/src/concepts/memory-manager.md b/docs/src/concepts/memory-manager.md index 04c18c9f..7f2e3855 100644 --- a/docs/src/concepts/memory-manager.md +++ b/docs/src/concepts/memory-manager.md @@ -47,10 +47,11 @@ The `MemoryManager` provides a `reclaim_memory` method to efficiently handle the ```rust use ic_stable_structures::{ memory_manager::{MemoryId, MemoryManager}, - BTreeMap, DefaultMemoryImpl, + BTreeMap, DefaultMemoryImpl, Memory, }; -let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); +let mem = DefaultMemoryImpl::default(); +let mem_mgr = MemoryManager::init(mem.clone()); let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== @@ -61,10 +62,13 @@ map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a +let size_before_migration = mem.size(); let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B allocates NEW memory +let size_after_migration = mem.size(); // Result: 2x memory usage +assert!(size_before_migration < size_after_migration); // ======================================== // Scenario 2: WITH reclamation @@ -72,12 +76,16 @@ map_b.insert(1, data.unwrap()); // B allocates NEW memory let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration +map_a.clear_new(); // A is now empty drop(map_a); // Drop A completely +let size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets +let size_after_migration = mem.size(); // Result: 1x memory usage +assert!(size_before_migration == size_after_migration); ``` ```admonish info "" From 09b60a758d4835fb414c4d398b8e0db5c30475e7 Mon Sep 17 00:00:00 2001 From: Maksym Arutyunyan Date: Wed, 20 Aug 2025 14:05:19 +0200 Subject: [PATCH 2/4] add virtual memory size asserts --- README.md | 21 +++++++++++++++------ docs/src/concepts/memory-manager.md | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 35172703..91ba4d9c 100644 --- a/README.md +++ b/README.md @@ -118,23 +118,28 @@ let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== // Scenario 1: WITHOUT reclamation // ======================================== -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let virtual_memory_a = mem_mgr.get(mem_id_a); +let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a let size_before_migration = mem.size(); -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); +let virtual_memory_b = mem_mgr.get(mem_id_b); +let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B allocates NEW memory let size_after_migration = mem.size(); - // Result: 2x memory usage -assert!(size_before_migration < size_after_migration); + // Result: ~2x memory usage +assert_eq!(virtual_memory_a.size(), virtual_memory_b.size()); +assert!(virtual_memory_a.size() <= size_before_migration); +assert!(virtual_memory_a.size() + virtual_memory_b.size() <= size_after_migration); // ======================================== // Scenario 2: WITH reclamation // ======================================== -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let virtual_memory_a = mem_mgr.get(mem_id_a); +let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty @@ -142,10 +147,14 @@ drop(map_a); // Drop A completely let size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); +let virtual_memory_b = mem_mgr.get(mem_id_b); +let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets let size_after_migration = mem.size(); // Result: 1x memory usage +assert_eq!(virtual_memory_a.size(), 0); +assert!(virtual_memory_a.size() < virtual_memory_b.size()); +assert!(virtual_memory_b.size() <= size_before_migration); assert!(size_before_migration == size_after_migration); ``` diff --git a/docs/src/concepts/memory-manager.md b/docs/src/concepts/memory-manager.md index 7f2e3855..1df0231e 100644 --- a/docs/src/concepts/memory-manager.md +++ b/docs/src/concepts/memory-manager.md @@ -57,23 +57,28 @@ let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== // Scenario 1: WITHOUT reclamation // ======================================== -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let virtual_memory_a = mem_mgr.get(mem_id_a); +let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a let size_before_migration = mem.size(); -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); +let virtual_memory_b = mem_mgr.get(mem_id_b); +let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B allocates NEW memory let size_after_migration = mem.size(); - // Result: 2x memory usage -assert!(size_before_migration < size_after_migration); + // Result: ~2x memory usage +assert_eq!(virtual_memory_a.size(), virtual_memory_b.size()); +assert!(virtual_memory_a.size() <= size_before_migration); +assert!(virtual_memory_a.size() + virtual_memory_b.size() <= size_after_migration); // ======================================== // Scenario 2: WITH reclamation // ======================================== -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let virtual_memory_a = mem_mgr.get(mem_id_a); +let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty @@ -81,10 +86,14 @@ drop(map_a); // Drop A completely let size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); +let virtual_memory_b = mem_mgr.get(mem_id_b); +let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets let size_after_migration = mem.size(); // Result: 1x memory usage +assert_eq!(virtual_memory_a.size(), 0); +assert!(virtual_memory_a.size() < virtual_memory_b.size()); +assert!(virtual_memory_b.size() <= size_before_migration); assert!(size_before_migration == size_after_migration); ``` From 9fa69a0afc0d13ef8c0ba276b3cafcb60cc8319a Mon Sep 17 00:00:00 2001 From: Maksym Arutyunyan Date: Wed, 20 Aug 2025 14:06:29 +0200 Subject: [PATCH 3/4] . --- docs/src/concepts/memory-manager.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/concepts/memory-manager.md b/docs/src/concepts/memory-manager.md index 1df0231e..8759073c 100644 --- a/docs/src/concepts/memory-manager.md +++ b/docs/src/concepts/memory-manager.md @@ -63,16 +63,16 @@ map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a -let size_before_migration = mem.size(); +let actual_size_before_migration = mem.size(); let virtual_memory_b = mem_mgr.get(mem_id_b); let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B allocates NEW memory -let size_after_migration = mem.size(); +let actual_size_after_migration = mem.size(); // Result: ~2x memory usage assert_eq!(virtual_memory_a.size(), virtual_memory_b.size()); -assert!(virtual_memory_a.size() <= size_before_migration); -assert!(virtual_memory_a.size() + virtual_memory_b.size() <= size_after_migration); +assert!(virtual_memory_a.size() <= actual_size_before_migration); +assert!(virtual_memory_a.size() + virtual_memory_b.size() <= actual_size_after_migration); // ======================================== // Scenario 2: WITH reclamation @@ -83,18 +83,18 @@ map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Drop A completely -let size_before_migration = mem.size(); +let actual_size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse let virtual_memory_b = mem_mgr.get(mem_id_b); let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets -let size_after_migration = mem.size(); +let actual_size_after_migration = mem.size(); // Result: 1x memory usage assert_eq!(virtual_memory_a.size(), 0); assert!(virtual_memory_a.size() < virtual_memory_b.size()); -assert!(virtual_memory_b.size() <= size_before_migration); -assert!(size_before_migration == size_after_migration); +assert!(virtual_memory_b.size() <= actual_size_before_migration); +assert!(actual_size_before_migration == actual_size_after_migration); ``` ```admonish info "" From 968b390482a5de4dd225e622b124dadd068dc363 Mon Sep 17 00:00:00 2001 From: Maksym Arutyunyan Date: Wed, 20 Aug 2025 14:18:47 +0200 Subject: [PATCH 4/4] . --- README.md | 31 +++++++++++------------------ docs/src/concepts/memory-manager.md | 21 +++++++------------ 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 91ba4d9c..c84f28fd 100644 --- a/README.md +++ b/README.md @@ -118,44 +118,37 @@ let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== // Scenario 1: WITHOUT reclamation // ======================================== -let virtual_memory_a = mem_mgr.get(mem_id_a); -let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a -let size_before_migration = mem.size(); +let actual_size_before_migration = mem.size(); -let virtual_memory_b = mem_mgr.get(mem_id_b); -let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B allocates NEW memory -let size_after_migration = mem.size(); +let actual_size_after_migration = mem.size(); // Result: ~2x memory usage -assert_eq!(virtual_memory_a.size(), virtual_memory_b.size()); -assert!(virtual_memory_a.size() <= size_before_migration); -assert!(virtual_memory_a.size() + virtual_memory_b.size() <= size_after_migration); + // Memory allocation grew (waste) +assert!(actual_size_before_migration < actual_size_after_migration); // ======================================== // Scenario 2: WITH reclamation // ======================================== -let virtual_memory_a = mem_mgr.get(mem_id_a); -let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Drop A completely -let size_before_migration = mem.size(); +let actual_size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse -let virtual_memory_b = mem_mgr.get(mem_id_b); -let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets -let size_after_migration = mem.size(); +let actual_size_after_migration = mem.size(); // Result: 1x memory usage -assert_eq!(virtual_memory_a.size(), 0); -assert!(virtual_memory_a.size() < virtual_memory_b.size()); -assert!(virtual_memory_b.size() <= size_before_migration); -assert!(size_before_migration == size_after_migration); + // Memory allocation stayed the same (no waste) +assert!(actual_size_before_migration == actual_size_after_migration); ``` **Important**: Always drop the original structure before calling `reclaim_memory`. diff --git a/docs/src/concepts/memory-manager.md b/docs/src/concepts/memory-manager.md index 8759073c..bf4452b9 100644 --- a/docs/src/concepts/memory-manager.md +++ b/docs/src/concepts/memory-manager.md @@ -57,28 +57,24 @@ let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); // ======================================== // Scenario 1: WITHOUT reclamation // ======================================== -let virtual_memory_a = mem_mgr.get(mem_id_a); -let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty drop(map_a); // Memory stays allocated to mem_id_a let actual_size_before_migration = mem.size(); -let virtual_memory_b = mem_mgr.get(mem_id_b); -let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B allocates NEW memory let actual_size_after_migration = mem.size(); // Result: ~2x memory usage -assert_eq!(virtual_memory_a.size(), virtual_memory_b.size()); -assert!(virtual_memory_a.size() <= actual_size_before_migration); -assert!(virtual_memory_a.size() + virtual_memory_b.size() <= actual_size_after_migration); + // Memory allocation grew (waste) +assert!(actual_size_before_migration < actual_size_after_migration); // ======================================== // Scenario 2: WITH reclamation // ======================================== -let virtual_memory_a = mem_mgr.get(mem_id_a); -let mut map_a: BTreeMap = BTreeMap::init(virtual_memory_a.clone()); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); map_a.insert(1, b'A'); // Populate map A with data let data = map_a.get(&1); // Extract data for migration map_a.clear_new(); // A is now empty @@ -86,14 +82,11 @@ drop(map_a); // Drop A completely let actual_size_before_migration = mem.size(); mem_mgr.reclaim_memory(mem_id_a); // Free A's memory buckets for reuse -let virtual_memory_b = mem_mgr.get(mem_id_b); -let mut map_b: BTreeMap = BTreeMap::init(virtual_memory_b.clone()); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_b.insert(1, data.unwrap()); // B reuses A's reclaimed memory buckets let actual_size_after_migration = mem.size(); // Result: 1x memory usage -assert_eq!(virtual_memory_a.size(), 0); -assert!(virtual_memory_a.size() < virtual_memory_b.size()); -assert!(virtual_memory_b.size() <= actual_size_before_migration); + // Memory allocation stayed the same (no waste) assert!(actual_size_before_migration == actual_size_after_migration); ```