robclu / leapfrog

Lock-free concurrent and single-threaded hash map implementations using Leapfrog probing. Currently the highest performance concurrent HashMap in Rust for certain use cases.
Apache License 2.0
202 stars 9 forks source link

Fastest, but with memory leaks. #16

Open lithbitren opened 1 month ago

lithbitren commented 1 month ago

While benchmarking using leapfrog, I found that its performance is indeed the fastest among a dozen third-party libraries. However, due to the repetitive nature of the tests, I noticed some implementations suffer from memory leaks. Unfortunately, leapfrog::LeapMap is also one of them, exhibiting leaks on both Windows 10 and Ubuntu 24.04. To verify this leakage, I crafted a dedicated test program. Here's my code:

cargo.toml:

[dependencies]
leapfrog = "0.3.0"
sysinfo = "0.30.13"

main.rs:

use sysinfo::{ System, Pid };

fn main() {
    println!("Process ID: {}", std::process::id());
    let pid = Pid::from_u32(std::process::id() as u32);

    let get_memory_usage = |pid| {
        let system_info = System::new_all();
        let current_process = system_info.process(pid).expect("Process not found");
        current_process.memory()
    };
    println!("Initial memory usage: {} bytes", get_memory_usage(pid));

    const NUM_ROUNDS: usize = 10;
    const INSERTIONS_PER_ROUND: usize = 10_000_000;

    let start_time = std::time::Instant::now();
    for round in 1..=NUM_ROUNDS {
        let map = leapfrog::LeapMap::new();
        for index in 0..INSERTIONS_PER_ROUND {
            map.insert(index, index);
        }
        let memory_before_drop = get_memory_usage(pid);
        drop(map); // Explicitly drop to free resources.
        let memory_after_drop = get_memory_usage(pid);
        let elapsed_time = start_time.elapsed();
        println!(
            "Round: {round}, Time elapsed: {:?}, Memory before drop: {} bytes, after drop: {} bytes",
            elapsed_time, memory_before_drop, memory_after_drop
        );
    }
}

The test output is as follows:

Initial memory usage: 11919360 bytes
Round: 1, Time elapsed: 1.8845246s, Memory before drop: 886329344 bytes, after drop: 232243200 bytes
Round: 2, Time elapsed: 3.826265s, Memory before drop: 1104896000 bytes, after drop: 450646016 bytes
Round: 3, Time elapsed: 5.6983455s, Memory before drop: 1323261952 bytes, after drop: 669356032 bytes
Round: 4, Time elapsed: 7.5641729s, Memory before drop: 1541644288 bytes, after drop: 887300096 bytes
Round: 5, Time elapsed: 9.4544339s, Memory before drop: 1759789056 bytes, after drop: 1105842176 bytes
Round: 6, Time elapsed: 11.3552829s, Memory before drop: 1977909248 bytes, after drop: 1323757568 bytes
Round: 7, Time elapsed: 13.2239042s, Memory before drop: 2196258816 bytes, after drop: 1541931008 bytes
Round: 8, Time elapsed: 15.0954465s, Memory before drop: 2414407680 bytes, after drop: 1760301056 bytes
Round: 9, Time elapsed: 16.9596344s, Memory before drop: 2632687616 bytes, after drop: 1978085376 bytes
Round: 10, Time elapsed: 18.8209026s, Memory before drop: 2850766848 bytes, after drop: 2196283392 bytes

Under Windows 10 (rust 1.81.0-nightly), by increasing the number of creation-deletion cycles, my system memory can be filled up in just a few minutes.

robclu commented 1 month ago

Hey,

Thank you for this, it is definitely something that I need to loop into. I will try and reproduce and fix as soon as I get a chance.