#[macro_use] extern crate alloc; #[macro_use] extern crate ctor; use std::sync::Arc; use std::thread; use std::thread::sleep; use std::time::Duration; use alloc::alloc::GlobalAlloc; use alloc::alloc::Layout; use buddy_system_allocator::LockedHeap; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rand::{Rng, SeedableRng}; const SMALL_SIZE: usize = 8; const LARGE_SIZE: usize = 1024 * 1024; // 1M const ALIGN: usize = 8; /// Alloc small object #[inline] pub fn small_alloc(heap: &LockedHeap) { let layout = unsafe { Layout::from_size_align_unchecked(SMALL_SIZE, ALIGN) }; unsafe { let addr = heap.alloc(layout); heap.dealloc(addr, layout); } } /// Alloc large object #[inline] pub fn large_alloc(heap: &LockedHeap) { let layout = unsafe { Layout::from_size_align_unchecked(LARGE_SIZE, ALIGN) }; unsafe { let addr = heap.alloc(layout); heap.dealloc(addr, layout); } } /// Multithreads alloc random sizes of object #[inline] pub fn mutil_thread_random_size(heap: &'static LockedHeap) { const THREAD_SIZE: usize = 10; let mut threads = Vec::with_capacity(THREAD_SIZE); let alloc = Arc::new(heap); for i in 0..THREAD_SIZE { let prethread_alloc = alloc.clone(); let handle = thread::spawn(move || { // generate a random size of object use seed `i` to ensure the fixed // result of each turn let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(i as u64); // generate a random object size in range of [SMALL_SIZE ..= LARGE_SIZE] let layout = unsafe { Layout::from_size_align_unchecked(rng.gen_range(SMALL_SIZE..=LARGE_SIZE), ALIGN) }; let addr = unsafe { prethread_alloc.alloc(layout) }; // sleep for a while sleep(Duration::from_nanos((THREAD_SIZE - i) as u64)); unsafe { prethread_alloc.dealloc(addr, layout) } }); threads.push(handle); } drop(alloc); for t in threads { t.join().unwrap(); } } /// Multithread benchmark inspired by **Hoard** benchmark /// /// Warning: This benchmark generally needs long time to finish /// /// ---------------------------------------------------------------------- /// Hoard: A Fast, Scalable, and Memory-Efficient Allocator /// for Shared-Memory Multiprocessors /// Contact author: Emery Berger, http://www.cs.utexas.edu/users/emery // /// Copyright (c) 1998-2000, The University of Texas at Austin. /// /// This library is free software; you can redistribute it and/or modify /// it under the terms of the GNU Library General Public License as /// published by the Free Software Foundation, http://www.fsf.org. /// /// This library is distributed in the hope that it will be useful, but /// WITHOUT ANY WARRANTY; without even the implied warranty of /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU /// Library General Public License for more details. /// ---------------------------------------------------------------------- /// #[inline] pub fn thread_test() { const N_ITERATIONS: usize = 50; const N_OBJECTS: usize = 30000; const N_THREADS: usize = 10; const OBJECT_SIZE: usize = 1; #[derive(Clone)] struct Foo { pub a: i32, pub b: i32, } let mut threads = Vec::with_capacity(N_THREADS); for _i in 0..N_THREADS { let handle = thread::spawn(move || { // let a = new Foo * [nobjects / nthreads]; let mut a = Vec::with_capacity(N_OBJECTS / N_THREADS); for j in 0..N_ITERATIONS { // inner object: // a[i] = new Foo[objSize]; for k in 0..(N_OBJECTS / N_THREADS) { a.push(vec![ Foo { a: k as i32, b: j as i32 }; OBJECT_SIZE ]); // in order to prevent optimization delete allocation directly // FIXME: don't know whether it works or not a[k][0].a += a[k][0].b; } } // auto drop here }); threads.push(handle); } for t in threads { t.join().unwrap(); } } const ORDER: usize = 32; const MACHINE_ALIGN: usize = core::mem::size_of::(); /// for now 128M is needed /// TODO: reduce memory use const KERNEL_HEAP_SIZE: usize = 128 * 1024 * 1024; const HEAP_BLOCK: usize = KERNEL_HEAP_SIZE / MACHINE_ALIGN; static mut HEAP: [usize; HEAP_BLOCK] = [0; HEAP_BLOCK]; /// Use `LockedHeap` as global allocator #[global_allocator] static HEAP_ALLOCATOR: LockedHeap = LockedHeap::::new(); /// Init heap /// /// We need `ctor` here because benchmark is running behind the std enviroment, /// which means std will do some initialization before execute `fn main()`. /// However, our memory allocator must be init in runtime(use linkedlist, which /// can not be evaluated in compile time). And in the initialization phase, heap /// memory is needed. /// /// So the solution in this dilemma is to run `fn init_heap()` in initialization phase /// rather than in `fn main()`. We need `ctor` to do this. #[ctor] fn init_heap() { let heap_start = unsafe { HEAP.as_ptr() as usize }; unsafe { HEAP_ALLOCATOR .lock() .init(heap_start, HEAP_BLOCK * MACHINE_ALIGN); } } /// Entry of benchmarks pub fn criterion_benchmark(c: &mut Criterion) { // run benchmark c.bench_function("small alloc", |b| { b.iter(|| small_alloc(black_box(&HEAP_ALLOCATOR))) }); c.bench_function("large alloc", |b| { b.iter(|| large_alloc(black_box(&HEAP_ALLOCATOR))) }); c.bench_function("mutil thread random size", |b| { b.iter(|| mutil_thread_random_size(black_box(&HEAP_ALLOCATOR))) }); c.bench_function("threadtest", |b| b.iter(|| thread_test())); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches);