CodeBlanks Logo Icon CodeBlanks

Allocator

Write your own allocator with unsafe.

Final task: heap allocator

The expert level's first final task: a global allocator that counts all of the program's heap allocations. The GlobalAlloc trait requires two methods: alloc and dealloc. We'll wrap the system allocator and update atomic counters on every call:

use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};

struct Sayan;
static ADET: AtomicUsize = AtomicUsize::new(0);
static BAYT: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for Sayan {
    unsafe fn alloc(&self, l: Layout) -> *mut u8 {
        ADET.fetch_add(1, Ordering::Relaxed);
        BAYT.fetch_add(l.size(), Ordering::Relaxed);
        unsafe { System.alloc(l) }
    }
    unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
        unsafe { System.dealloc(p, l) }
    }
}

#[global_allocator]
static AYIRICI: Sayan = Sayan;

Measuring a block

The difference between the counters before and after a block gives that block's allocations. It's important not to print during the measurement, because println! may allocate too:

let (a0, b0) = (ADET.load(Ordering::Relaxed), BAYT.load(Ordering::Relaxed));
{
    let v: Vec<u8> = Vec::with_capacity(100);
    std::hint::black_box(&v);
}
let (a1, b1) = (ADET.load(Ordering::Relaxed), BAYT.load(Ordering::Relaxed));
println!("{} {}", a1 - a0, b1 - b0);   // 1 100

This technique is used to find needless allocations in hot loops; the dhat crate is a detailed version of the same idea.

Practice Counting allocator

Write and register the Sayan allocator above. Compute the allocation count and byte amount of a block creating Vec::<u8>::with_capacity(100), without printing during the measurement, and print them separated by a space.

Starter code

main.rs
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};

fn main() {
    // buraya yaz
}

Sample solution

main.rs
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};

struct Sayan;

static ADET: AtomicUsize = AtomicUsize::new(0);
static BAYT: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for Sayan {
    unsafe fn alloc(&self, l: Layout) -> *mut u8 {
        ADET.fetch_add(1, Ordering::Relaxed);
        BAYT.fetch_add(l.size(), Ordering::Relaxed);
        // SAFETY: sözleşme çağırandan System'e aynen aktarılır.
        unsafe { System.alloc(l) }
    }
    unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
        // SAFETY: p, aynı layout ile System'den alınmıştı.
        unsafe { System.dealloc(p, l) }
    }
}

#[global_allocator]
static AYIRICI: Sayan = Sayan;

fn main() {
    let (a0, b0) = (ADET.load(Ordering::Relaxed), BAYT.load(Ordering::Relaxed));
    {
        let v: Vec<u8> = Vec::with_capacity(100);
        std::hint::black_box(&v);
    }
    let (a1, b1) = (ADET.load(Ordering::Relaxed), BAYT.load(Ordering::Relaxed));
    println!("{} {}", a1 - a0, b1 - b0);
}

Expected output

1 100

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why is using <code>println!</code> or <code>Vec</code> inside the allocator dangerous?

  • They may allocate too; the allocator calls itself recursively and deadlocks or overflows Correct answer
  • It's slow
  • It doesn't compile
  • It isn't dangerous

Explanation: An allocator must only do work that needs no allocation: atomic counters are ideal.

2 Register the global allocator

Question
#[____]
static AYIRICI: Sayan = Sayan;
Answer
#[global_allocator]
static AYIRICI: Sayan = Sayan;

Explanation: #[global_allocator] routes all of the program's heap allocations to this value.

3 Item and role

Term Meaning
GlobalAlloc Allocator trait
Layout Size and alignment
System The OS allocator
#[global_allocator] Registration

All lessons in Expert