I was testing one program with Crossbeam Skiplist. I tried to use RWLock (Parking) with Hashmap as well But looks like memory is not being released. I tried with jemalloc but still same issue. Calling add on same set of keys is again just increasing the memory.
I know the not all the freed memory is return to os by allocator but if i am clearing and reinserting the keys it should not consume more memory.
Code
use std::collections::HashMap;
use actix_web::{HttpServer, web, get, App};
use parking_lot::RwLock;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(AppState {
data: RwLock::new(HashMap::new())
}))
.service(add)
.service(clear)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
struct AppState {
data: RwLock<HashMap<String, String>>,
}
#[get("/add")]
async fn add(data: web::Data<AppState>) -> String {
let max_entries = 100663296 as u64;
let mut m = data.data.write();
for i in 0..max_entries / 10 {
m.insert(format!("str-{i}"), format!("str-{i}-{i}"));
}
let size = m.len();
format!("count {size}! from thread {:?}\n", std::thread::current().id())
}
#[get("/clear")]
async fn clear(data: web::Data<AppState>) -> String {
let mut m = data.data.write();
m.clear();
let size = m.len();
format!("countt {size}! from thread {:?}\n", std::thread::current().id())
}
Cargo toml
[package]
name = "skiptest"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4"
parking_lot = "0.12.1"
HttpServer::new accepts an application factory rather than an application instance; the factory closure is called on each worker thread independently. Therefore, if you want to share a data object between different workers, a shareable object needs to be created first, outside the HttpServer::new closure and cloned into it. Data<T> is an example of such a sharable object.
Modifying your main function to comply with that gets the behavior you'd expect
You were calling clear on a different HashMap than the one that had been added to because different threads were servicing the requests and each thread was creating it's own private AppState.
Hi @semicoleon I am checking memory via htop. I performed the two runs , following are the values
Using map.clear()
Started - 4. KB
Added - 6828 MB
Clear - 6828 MB
Replacing map with new one ( *m = HashMap::new();)
Started - 4. KB
Added - 6828 MB
Replace - 3692 MB
Added - 10.3 GB
Replace - 7.4 GB
Looks like only half of the memory is being freed.
I am running the binary in release mode in aws ec2 instance with centos.
If I changed the add code to following still memory is being increased by 3GB per add call.
#[get("/add")]
async fn add(data: web::Data<AppState>) -> String {
let max_entries = 100663296 as u64;
let mut m = HashMap::new();
for i in 0..max_entries / 2 {
m.insert(format!("str-{i}"), format!("str-{i}-{i}"));
}
let size = m.len();
format!("count00 {size}! from thread {:?}\n", std::thread::current().id())
}
I can recommend the dhat crate. It's let's you replace the global allocator and gives you a detailed allocation report that you can view in a web viewer.
The setup is very minimal.
I'm not exactly sure it applies to your problem, but it shows you where allocations happen with stack traces.
You can try calling malloc_trim after you clear/reset the map to see if that gets you more memory back.
Another thing to try would be modifying the CLI version to remove anything that could possibly allocate between when you clear the map and check the memory.
In actix web version, without trim here is the memory usage on 30GB machine. It eventually stopped allocating more memory after 17.5 GB for the same runs.
curl --location --request GET '127.0.0.1:8080/add'
count 50331648! from thread ThreadId(18)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 50331648, Capacity: 58720256, Memory: 6.7 GiB, Virtual: 8.3 GiB
curl --location --request GET '127.0.0.1:8080/clear'
countt 0! from thread ThreadId(20)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 0, Capacity: 58720256, Memory: 6.7 GiB, Virtual: 8.3 GiB
curl --location --request GET '127.0.0.1:8080/add'
count 50331648! from thread ThreadId(22)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 50331648, Capacity: 58720256, Memory: 10.3 GiB, Virtual: 11.8 GiB
curl --location --request GET '127.0.0.1:8080/clear'
countt 0! from thread ThreadId(24)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 0, Capacity: 58720256, Memory: 10.3 GiB, Virtual: 11.8 GiB
curl --location --request GET '127.0.0.1:8080/add'
count 50331648! from thread ThreadId(20)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 50331648, Capacity: 58720256, Memory: 13.9 GiB, Virtual: 15.4 GiB
curl --location --request GET '127.0.0.1:8080/clear'
countt 0! from thread ThreadId(23)
curl --location --request GET '127.0.0.1:8080/stats'
Length: 0, Capacity: 58720256, Memory: 13.9 GiB, Virtual: 15.4 GiB
curl --location --request GET '127.0.0.1:8080/stats'
Length: 50331648, Capacity: 58720256, Memory: 17.5 GiB, Virtual: 18.9 GiB
After 17.5 GiB - it stopped allocating more memory
I will go back and try with my original problem statement to run same thing with crossbeam skipmap.