18

I have a web application written in Rust and wasm-bindgen that needs to store state. The state is stored like this:

lazy_static! {
    static ref ID_TO_DATA: Mutex<HashMap<u32, Data>> = Mutex::new(HashMap::new());
}

pub struct Data {
    pub coder_id: u16,
    pub bools: Vec<bool>,
    pub ints: Vec<i32>,
    pub strings: Vec<String>,
}

I attempted the following to remove the data and free the memory, and the data is removed from the HashMap and no errors are reported:

#[wasm_bindgen]
pub fn remove_data(id: u32) {
    match ID_TO_DATA.lock().unwrap().remove(&id) {
        Some(data) => {
            std::mem::drop(data);
        }
        None => {}
    }
}

However, the memory used by the browser tab never drops (using Chrome 67). I used Windows' Task Manager and watched the memory grow to almost 2GB for the relevant process/tab, and then after my program removed all the entries, I waited a minute and the memory was still at almost 2GB.

I also tried the following, but got this error: RuntimeError: memory access out of bounds

#[wasm_bindgen]
pub fn remove_data(id: u32) {
    match ID_TO_DATA.lock().unwrap().remove(&id) {
        Some(mut data) => {
            unsafe {
                std::ptr::drop_in_place(&mut data);
            }
        }
        None => {}
    }
}

How can I successfully free this memory?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Adam R
  • 565
  • 4
  • 11
  • 3
    Can you show us how you measured memory usage in the browser? You might also want to know [how `std::mem::drop` is implemented](https://github.com/rust-lang/rust/blob/master/src/libcore/mem.rs#L795), so as to avoid misusage. – E_net4 Jul 26 '18 at 17:19
  • 2
    As E_net4 alludes, your `remove_data` method can be written as `ID_TO_DATA.lock().unwrap().remove(&id);`. No calls to `drop`, no `match`. – Shepmaster Jul 26 '18 at 17:23
  • 3
    In general (not just in Rust), freed memory is not restored to the operating system, but to the program's allocator. See, e.g., [Python memory releasing very slowly](https://stackoverflow.com/questions/48690301/python-memory-releasing-very-slowly), [Linux Allocator Does Not Release Small Chunks of Memory](https://stackoverflow.com/questions/10943907/linux-allocator-does-not-release-small-chunks-of-memory), and [In Perl, how can I release memory to the operating system?](https://stackoverflow.com/questions/1242832/in-perl-how-can-i-release-memory-to-the-operating-system) – trent Jul 26 '18 at 17:39
  • Thanks to @trentcl, I thought I'd try it again with multiple cycles of adding / removing data, and although the browser memory never drops significantly, it doesn't keep growing, so I can only assume the memory is being reused by WebAssembly. This is sufficient for my needs. – Adam R Jul 26 '18 at 17:55

1 Answers1

20

WebAssembly does not offer any instructions to deallocate memory, there is only the ability to increase the allocated size. Practically speaking, this means that the peak memory usage of your WebAssembly application is also the permanent memory usage.

For a given problem, it may be possible to tweak your algorithm to reduce the peak amount of memory.

I don't have the knowledge or ability to test this, but one out-of-the-box idea would be to try and have multiple WebAssembly runtimes distinct from each other. You could chew up a lot of memory in one to compute a relatively small result, serialize that result outside of the WASM runtime, then throw it away and spin up a new one. This is likely only to be useful in certain specific problem domains.


In the future, memory resizing may be re-added to WebAssembly. It was explicitly removed before the MVP release:

After the MVP, we are moving to things that diverge and cannot be polyfilled, and memory resizing makes more sense to add at that point in time.

Thanks to alexcrichton and steveklabnik for answering this question in the Rust Discord.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366