Rust
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.
Exercise 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.
Show example solution
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);
}
Test yourself
-
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
- It's slow
- It doesn't compile
- It isn't dangerous
Show answer
They may allocate too; the allocator calls itself recursively and deadlocks or overflows — An allocator must only do work that needs no allocation: atomic counters are ideal.
-
Register the global allocator
#[____]
static AYIRICI: Sayan = Sayan;
Show answer
#[global_allocator]
static AYIRICI: Sayan = Sayan;
-
Item and role
- GlobalAlloc
- Allocator trait
- Layout
- Size and alignment
- System
- The OS allocator
- #[global_allocator]
- Registration
Next lesson: SIMD matrix