Memory allocation and usage in Rust

Rust

I wanted to check and report how much memory my process uses. So far I could not find a way to get that informations so I settled the second best thing, to report how much memory is used and how much is free in the whole computer.

Update: See how much memory does my process use.

Dependencies in Cargo.toml

For this I used the sysinfo crate. I also used the thousands crate to commafy the big numbers to make them more readable.

examples/memory-allocation/Cargo.toml

[package]
name = "memory-allocation"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sysinfo = "0.30"
thousands = "0.2"

The full code

examples/memory-allocation/src/main.rs

use sysinfo::System;
use thousands::Separable;

fn main() {
    let filler = "0123456789".repeat(100);
    let args = std::env::args().collect::<Vec<String>>();
    let size :usize = if args.len() == 1 {
        20_000_000
    } else {
        args[1].parse().unwrap()
    };

    println!(
        "Size: {} filler: {} total: {} bytes\n",
        size.separate_with_commas(),
        filler.len(),
        (size * filler.len()).separate_with_commas()
    );

    println!("Total memory:                           {:>15}",  total_memory().separate_with_commas());
    let used_before = used_memory();
    println!("Used memory before allocation:          {:>15}", used_before.separate_with_commas());

    let used_allocated = allocate(size, &filler);
    println!("Used memory after allocation:           {:>15}", used_allocated.separate_with_commas());

    let used_after = used_memory();
    println!("Used memory after deallocation:         {:>15}", used_after.separate_with_commas());

    if used_allocated > used_before {
        println!("Memory used by allocation (diff):       {:>15}", (used_allocated - used_before).separate_with_commas());
    } else {
        println!("Memory freed after allocation (diff):   {:>15}", (used_before - used_allocated).separate_with_commas());
    }
    if used_allocated > used_after {
        println!("Memory freed by deallocation (diff):    {:>15}", (used_allocated - used_after).separate_with_commas());
    } else {
        println!("Memory used by deallocation (diff):     {:>15}", (used_after - used_allocated).separate_with_commas());
    }
}

fn allocate(size: usize, filler: &str) -> u64 {

    let mut text = String::with_capacity(size * filler.len());
    for _ in 0..size {
        text.push_str(filler);
    }

    used_memory()
}


fn total_memory() -> u64 {
    let mut sys = System::new_all();
    sys.refresh_all();
    sys.total_memory()
}

fn used_memory() -> u64 {
    let mut sys = System::new_all();
    sys.refresh_all();
    sys.used_memory()
}




The user can provide a number and the code will allocate 1,000 times that many bytes.

First we measure and display the total size of the memory in the computer.

Then we show the size of the used memory. This is the baseline.

Then we create a string containing the 1,000 bytes long filler times the number the user provided. This will take up some memory in the computer. We measure the size of the used memory inside the allocate function while the variable with the long sting is still in scope.

Finally, after leaving the allocate function the variable goes out of scope and thus Rust can deallocate the memory use by that long string. At this point we measure the used memory again.

After we have done this we show how much did the used memory grow with the data allocation and how much did it shrink after the deallocation.

Result

My computer has 32 Gb memory though htop and the free command only repor 27Gb. Strange.

$ free
               total        used        free      shared  buff/cache   available
Mem:        28491496     4068548    23020620      166596     2005024    24422948
Swap:        8388604     3938792     4449812

Allocating too much data will crash the process:

$ cargo run -q 40000000
Size: 40,000,000 filler: 1000 total: 40,000,000,000 bytes

Total memory:                            29,175,291,904
Used memory before allocation:            4,136,079,360
memory allocation of 40000000000 bytes failed
Aborted (core dumped)

I am not sure why in one case it prints Aborted (core dumped) and in another case it prints Killed.

$ cargo run -q 35000000
Size: 35,000,000 filler: 1000 total: 35,000,000,000 bytes

Total memory:                            29,175,291,904
Used memory before allocation:            4,168,376,320
Killed

IF the data fits into the memroy then we can see the changes by the allocation and deallocation of data both being round 20 Gb.

$ cargo run -q 20000000
Size: 20,000,000 filler: 1000 total: 20,000,000,000 bytes

Total memory:                            29,175,291,904
Used memory before allocation:            3,206,164,480
Used memory after allocation:            23,201,665,024
Used memory after deallocation:           3,182,731,264
Memory used by allocation (diff):        19,995,500,544
Memory freed by deallocation (diff):     20,018,933,760

Strange results

When we try to allocate only a few bytes we get some really strange results.

$ cargo run -q 2
Size: 2 filler: 1000 total: 2,000 bytes

Total memory:                            29,175,291,904
Used memory before allocation:            3,296,440,320
Used memory after allocation:             3,276,918,784
Used memory after deallocation:           3,279,728,640
Memory freed after allocation (diff):        19,521,536
Memory used by deallocation (diff):           2,809,856

In this example we only allocate 2*1000 bytes, but that still means our process is using memory. Despite that it seems that while we are using more memory, the total used memory got smaller and while we free our 2,000 bytes the system started to use more memory.

That seems to be totally incorrect.

I can think of two explanations:

  1. Either our measurements are not exact and so this is a measuring error.
  2. Other processes that also run in the system (e.g. my browser, dropbox, or even the terminal) also allocate and deallocate memory while my experiment runs.

Conclusion

The sysinfo crate can help us understand the size of the used and the free memory of the system, which can be very useful, but we have to be carful not conclude much about the memory usage of our current process as other processes will impact the numbers.

We might be able to use this to avoid filling the memory of the computer and thus crashing, but it would be better to have a way to know how much memory our current process is using.

Related Pages

How much memory does my Rust process use?

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo