How to set a free strategy? - linux

I've encountered high memory usage (looks like a memory leak) in production environment (container in k8s), and want to check if it's because of the "MADV_FREE" behaviour.
Is there a way to change to use MADV_DONTNEED instead of MADV_FREE in rust?

Rust allows overriding the default allocator with the #[global_allocator] attribute
struct MyAllocator;
unsafe impl GlobalAlloc for MyAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
}
#[global_allocator]
static GLOBAL: MyAllocator = MyAllocator;
You could use this to change the behavior of the deallocation to your needs.
Or possably use an existing crate that impliment allocators tha logs the allocations/deallocations such as tracing-allocator or logging-allocator.
#[global_allocator]
static GLOBAL: tracing_allocator::Allocator = tracing_allocator::Allocator{};
fn main() {
let f = File::create("trace.txt").unwrap();
tracing_allocator::Allocator::initialize(&f);
tracing_allocator::Allocator::activate();
}
(I have no experience with these crates so I have no idea what they uses for allocations)

Related

Keep vector aligned to page size [duplicate]

I'd like to align my heap memory to a specific alignment boundary. This boundary is known at compilation time. Anyhow, the Box abstraction doesn't allow me to specify a certain alignment. Writing my own Box-abstraction doesn't feel right too, because all of the Rust ecosystem uses Box already. What is a convenient way to achieve alignment for heap allocations?
PS: In my specific case I need page alignments.
If nightly is acceptable, the Allocator api provides a fairly convenient way to do this:
#![feature(allocator_api)]
use std::alloc::*;
use std::ptr::NonNull;
struct AlignedAlloc<const N: usize>;
unsafe impl<const N: usize> Allocator for AlignedAlloc<N> {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
Global.allocate(layout.align_to(N).unwrap())
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
Global.deallocate(ptr, layout.align_to(N).unwrap())
}
}
fn main() {
let val = Box::new_in(3, AlignedAlloc::<4096>);
let ptr: *const u8 = &*val;
println!(
"val:{}, alignment:{}",
val,
1 << (ptr as usize).trailing_zeros()
);
}
Playground
If you wanted you could also add support for using other allocators or choosing the value dynamically.
Edit: Come to think of it, this approach can also be used in stable via the GlobalAlloc trait and the #[global_allocator] attribute, but setting an allocator that way forces all allocations to be aligned to that same alignment, so it would be fine if you needed to ensure a relatively small alignment, but it is probably not a good idea for a page boundary alignment.
Edit 2: switched from using the System allocator to the Global allocator, because that's bundled in with the allocater_api feature, and it is a more sensible and predictable default.
Depending on what data you want to align, you can use repr (align) to specify the alignment of a type. You can either use that directly on your data or use a wrapper struct.
For example, the following will always be aligned on 16 bytes (whether on the heap, on the stack or inside another struct):
#[repr (C, align (16))]
struct AlignedBuffer([u8; 32]);
Posting an alternative answer just in case folks are curious. I'm not a Rust export but this seems to work:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4c2d9c936e2755d1fc835d1e4663ec8b
#![feature(allocator_api)]
use std::{
alloc::{Layout, alloc_zeroed, handle_alloc_error}
};
const PAGE_SIZE: usize = 8192;
const NUM_PAGES: usize = 1000;
#[derive(Debug)]
struct Pool {
frames: Box<[[u8; PAGE_SIZE]; NUM_PAGES]>
}
impl Pool {
pub fn new() -> Self {
Self {
frames: unsafe {
let layout = Layout::from_size_align(NUM_PAGES * PAGE_SIZE, PAGE_SIZE).unwrap();
let ptr = alloc_zeroed(layout);
if ptr.is_null() {
handle_alloc_error(layout);
}
let ptr = ptr as *mut [[u8; PAGE_SIZE]; NUM_PAGES];
Box::from_raw(ptr)
},
}
}
}

Rust: allocator API: expected struct `std::alloc::Global` when I provide my own allocator

I want to use my own allocator in alloc::vec::Vec::new_in. However, Rust tells me "expected struct std::alloc::Global". How can I use my own allocator? I'm on Rust nightly 1.60.
#![feature(allocator_api)]
use std::alloc::{Allocator, AllocError, Layout};
use std::ptr::NonNull;
struct PageAlignedAlloc;
unsafe impl Allocator for PageAlignedAlloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
std::alloc::System.allocate(layout.align_to(4096).unwrap())
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
std::alloc::System.deallocate(ptr, layout)
}
}
fn main() {
let vec = Vec::<u8>::new_in(PageAlignedAlloc);
}
alloc::vec::Vec uses the Global allocator as the default for the second generic parameter. You can see this in the source code of the alloc library:
pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
buf: RawVec<T, A>,
len: usize,
}
This is the reason for the error message.
To compile your code, you need to provide a full type definition like this instead:
Original answer
let vec = Vec::<u8, PageAlignedAlloc>::new_in(PageAlignedAlloc);
I don't know if there are any issues regarding that. I think the Rust compiler should be capable of inferring the type by itself in that case in the future.
Updated, thanks to #eggyal 's comment
let vec = Vec::<u8, _>::new_in(PageAlignedAlloc);

How can I align memory on the heap (in a box) in a convenient way?

I'd like to align my heap memory to a specific alignment boundary. This boundary is known at compilation time. Anyhow, the Box abstraction doesn't allow me to specify a certain alignment. Writing my own Box-abstraction doesn't feel right too, because all of the Rust ecosystem uses Box already. What is a convenient way to achieve alignment for heap allocations?
PS: In my specific case I need page alignments.
If nightly is acceptable, the Allocator api provides a fairly convenient way to do this:
#![feature(allocator_api)]
use std::alloc::*;
use std::ptr::NonNull;
struct AlignedAlloc<const N: usize>;
unsafe impl<const N: usize> Allocator for AlignedAlloc<N> {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
Global.allocate(layout.align_to(N).unwrap())
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
Global.deallocate(ptr, layout.align_to(N).unwrap())
}
}
fn main() {
let val = Box::new_in(3, AlignedAlloc::<4096>);
let ptr: *const u8 = &*val;
println!(
"val:{}, alignment:{}",
val,
1 << (ptr as usize).trailing_zeros()
);
}
Playground
If you wanted you could also add support for using other allocators or choosing the value dynamically.
Edit: Come to think of it, this approach can also be used in stable via the GlobalAlloc trait and the #[global_allocator] attribute, but setting an allocator that way forces all allocations to be aligned to that same alignment, so it would be fine if you needed to ensure a relatively small alignment, but it is probably not a good idea for a page boundary alignment.
Edit 2: switched from using the System allocator to the Global allocator, because that's bundled in with the allocater_api feature, and it is a more sensible and predictable default.
Depending on what data you want to align, you can use repr (align) to specify the alignment of a type. You can either use that directly on your data or use a wrapper struct.
For example, the following will always be aligned on 16 bytes (whether on the heap, on the stack or inside another struct):
#[repr (C, align (16))]
struct AlignedBuffer([u8; 32]);
Posting an alternative answer just in case folks are curious. I'm not a Rust export but this seems to work:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4c2d9c936e2755d1fc835d1e4663ec8b
#![feature(allocator_api)]
use std::{
alloc::{Layout, alloc_zeroed, handle_alloc_error}
};
const PAGE_SIZE: usize = 8192;
const NUM_PAGES: usize = 1000;
#[derive(Debug)]
struct Pool {
frames: Box<[[u8; PAGE_SIZE]; NUM_PAGES]>
}
impl Pool {
pub fn new() -> Self {
Self {
frames: unsafe {
let layout = Layout::from_size_align(NUM_PAGES * PAGE_SIZE, PAGE_SIZE).unwrap();
let ptr = alloc_zeroed(layout);
if ptr.is_null() {
handle_alloc_error(layout);
}
let ptr = ptr as *mut [[u8; PAGE_SIZE]; NUM_PAGES];
Box::from_raw(ptr)
},
}
}
}

How to add/subtract an offset to/from NonNull<Opaque>?

I provide two functions for managing memory:
unsafe extern "system" fn alloc<A: Alloc>(
size: usize,
alignment: usize,
) -> *mut c_void { ... }
unsafe extern "system" fn free<A: Alloc>(
memory: *mut c_void
) { ... }
Both functions internally use the allocator-api.
These signatures cannot be changed. The problem is that free does not ask for size and alignment, which is required for Alloc::dealloc. To get around this, alloc allocates some extra space for one Layout. free can now access this Layout to get the needed extra data.
Recently, the allocator-api changed and instead of *mut u8 it now uses NonNull<Opaque>. This is where my problem occurs.
core::alloc::Opaque:
An opaque, unsized type. Used for pointers to allocated memory. [...] Such pointers are similar to C’s void* type.
Opaque is not Sized, so the use of NonNull::as_ptr().add() and NonNull::as_ptr().sub() are forbidden.
Previously, I used something like this (for simplicity, I assume Alloc's functions to be static):
#![feature(allocator_api)]
#![no_std]
extern crate libc;
use core::alloc::{Alloc, Layout};
use libc::c_void;
unsafe extern "system" fn alloc<A: Alloc>(
size: usize,
alignment: usize,
) -> *mut c_void
{
let requested_layout =
Layout::from_size_align(size, alignment).unwrap();
let (layout, padding) = Layout::new::<Layout>()
.extend_packed(requested_layout)
.unwrap();
let ptr = A::alloc(layout).unwrap();
(ptr as *mut Layout).write(layout);
ptr.add(padding)
}
The last line is not possible anymore with NonNull<Opaque>. How I can get around this?
I'd probably write it like this, using NonNull::as_ptr to get a *mut Opaque and then cast that to different concrete types:
#![feature(allocator_api)]
#![no_std]
extern crate libc;
use core::alloc::{Alloc, Layout};
use libc::c_void;
unsafe fn alloc<A: Alloc>(allocator: &mut A, size: usize, alignment: usize) -> *mut c_void {
let requested_layout = Layout::from_size_align(size, alignment).expect("Invalid layout");
let (layout, _padding) = Layout::new::<Layout>()
.extend_packed(requested_layout)
.expect("Unable to create layout");
let ptr = allocator.alloc(layout).expect("Unable to allocate");
// Get a pointer to our layout storage
let raw = ptr.as_ptr() as *mut Layout;
// Save it
raw.write(layout);
// Skip over it
raw.offset(1) as *mut _
}
unsafe extern "system" fn alloc<A: Alloc>(
This makes no sense to me. The various FFI ABIs ("C", "system", etc.) have no way of specifying Rust generic types. It seems deeply incorrect for this function to be marked extern.
Layout::new::<Layout>().extend_packed(requested_layout)
This seems likely to be very broken. As the documentation for Layout::extend_packed states, emphasis mine:
the alignment of next is irrelevant, and is not incorporated at all into the resulting layout.
Your returned pointer doesn't seem to honor the alignment request.

Adding lifetime constraints to non-reference types

I am trying to figure out how to apply Rust lifetimes to add some compile-time enforcement to Erlang NIF modules. NIF modules are shared libraries normally written in C that provide extensions.
A simplified prototype of the callback you would write in C looks like this:
Handle my_nif_function(Heap *heap, Handle handle);
You are provided a handle and a pointer to the heap that owns it. In your callback you may inspect the input handle, create more handles on the heap, and return one of them as the function return. The heap and all its handles become invalid after your callback returns, so you must not store copies of the heap or its handles during the callback. Unfortunately I’ve seen people do exactly this and it eventually results in a mysterious emulator crash. Can Rust enforce these lifetime constraints?
I think the heap can be easily managed by turning it into a reference.
fn my_nif_function(heap: &Heap, handle: Handle) -> Handle
But how can I link the lifetime of the input and output handles to the heap?
Another wrinkle to this is that you can also create your own heaps and handles which are allowed to live outside the scope of a callback invocation. In C++ I would use std::unique_ptr with a custom destructor. What is the Rust equivalent? The [simplified] C API for managing heaps looks like this:
Heap *create_heap();
void destroy_heap(Heap *);
Reference: NIFs are described here: http://www.erlang.org/doc/man/erl_nif.html . The Erlang names for "heaps" and "handles" are "environments" and "terms". I used the names "heaps" and "handles" so that the question would be more broadly understood.
Rust 1.0
The various marker types have been unified into one: PhantomData
use std::ptr;
use std::marker::PhantomData;
struct Heap {
ptr: *const u8,
}
impl Heap {
fn new(c_ptr: *const u8) -> Heap {
Heap {
ptr: c_ptr
}
}
fn wrap_handle<'a>(&'a self, c_handle: *const u8) -> Handle<'a> {
Handle {
ptr: c_handle,
marker: PhantomData,
}
}
}
struct Handle<'a> {
ptr: *const u8,
marker: PhantomData<&'a ()>,
}
fn main() {
let longer_heap = Heap::new(ptr::null());
let handle = {
let shorter_heap = Heap::new(ptr::null());
let longer_handle = longer_heap.wrap_handle(ptr::null());
let shorter_handle = shorter_heap.wrap_handle(ptr::null());
// longer_handle // ok to return
// shorter_handle // error: `shorter_heap` does not live long enough
};
}
Original Answer
Here's an example of using ContravariantLifetime. We wrap the raw heap pointer into a struct and then wrap raw handle pointers in another struct, reusing the lifetime of the heap.
use std::ptr;
use std::marker::ContravariantLifetime;
struct Heap {
ptr: *const u8,
}
impl Heap {
fn new(c_ptr: *const u8) -> Heap {
Heap {
ptr: c_ptr
}
}
fn wrap_handle<'a>(&'a self, c_handle: *const u8) -> Handle<'a> {
Handle {
ptr: c_handle,
marker: ContravariantLifetime,
}
}
}
struct Handle<'a> {
ptr: *const u8,
marker: ContravariantLifetime<'a>,
}
fn main() {
let longer_heap = Heap::new(ptr::null());
let handle = {
let shorter_heap = Heap::new(ptr::null());
let longer_handle = longer_heap.wrap_handle(ptr::null());
let shorter_handle = shorter_heap.wrap_handle(ptr::null());
// longer_handle // ok to return
// shorter_handle // error: `shorter_heap` does not live long enough
};
}
Lifetime markers
There are 3 lifetime markers. I won't attempt to replicate the reasonably good but dense documentation here, but can also point out the dense Wikipedia page, which might be some small assistance. I've listed them in the order that you are most likely to use them:
ContravariantLifetime
InvariantLifetime
CovariantLifetime

Resources