Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Jul 10, 2024
0 parents commit 0dc4c1d
Show file tree
Hide file tree
Showing 11 changed files with 843 additions and 0 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on: [push, pull_request]

jobs:
ci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rust-src, clippy, rustfmt
targets: ${{ matrix.targets }}
- name: Check rust version
run: rustc --version --verbose
- name: Check code format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- name: Build
run: cargo build --target ${{ matrix.targets }} --all-features
- name: Unit test
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
run: cargo test --target ${{ matrix.targets }} -- --nocapture

doc:
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
contents: write
env:
default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Build docs
continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
run: |
cargo doc --no-deps --all-features
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
- name: Deploy to Github Pages
if: ${{ github.ref == env.default-branch }}
uses: JamesIves/github-pages-deploy-action@v4
with:
single-commit: true
branch: gh-pages
folder: target/doc
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
/.vscode
.DS_Store
Cargo.lock
37 changes: 37 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "allocator"
version = "0.1.0"
edition = "2021"
authors = ["Yuekai Jia <[email protected]>"]
description = "Various allocator algorithms in a unified interface"
license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
homepage = "https://github.com/arceos-org/arceos"
repository = "https://github.com/arceos-org/allocator"
documentation = "https://arceos-org.github.io/allocator"

[features]
default = []
full = ["bitmap", "tlsf", "slab", "buddy", "allocator_api"]

bitmap = ["dep:bitmap-allocator"]

tlsf = ["dep:rlsf"]
slab = ["dep:slab_allocator"]
buddy = ["dep:buddy_system_allocator"]

allocator_api = []

[dependencies]
rlsf = { version = "0.2", optional = true }
buddy_system_allocator = { version = "0.10", default-features = false, optional = true }
slab_allocator = { git = "https://github.com/arceos-org/slab_allocator.git", tag = "v0.3.1", optional = true }
bitmap-allocator = { git = "https://github.com/rcore-os/bitmap-allocator.git", rev = "88e871a", optional = true }

[dev-dependencies]
allocator = { path = ".", features = ["full"] }
rand = { version = "0.8", features = ["small_rng"] }
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "collections"
harness = false
101 changes: 101 additions & 0 deletions benches/collections.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#![feature(allocator_api)]
#![feature(btreemap_alloc)]

mod utils;

use std::alloc::Allocator;
use std::collections::BTreeMap;
use std::io::Write;

use allocator::{AllocatorRc, BuddyByteAllocator, SlabByteAllocator, TlsfByteAllocator};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::{rngs::SmallRng, seq::SliceRandom, RngCore, SeedableRng};

use self::utils::MemoryPool;

const POOL_SIZE: usize = 1024 * 1024 * 128;

fn vec_push(n: usize, alloc: &(impl Allocator + Clone)) {
let mut v: Vec<u32, _> = Vec::new_in(alloc.clone());
for _ in 0..n {
v.push(0xdead_beef);
}
drop(v);
}

fn vec_rand_free(n: usize, blk_size: usize, alloc: &(impl Allocator + Clone)) {
let mut v = Vec::new_in(alloc.clone());
for _ in 0..n {
let block = Vec::<u64, _>::with_capacity_in(blk_size, alloc.clone());
v.push(block);
}

let mut rng = SmallRng::seed_from_u64(0xdead_beef);
let mut index = Vec::with_capacity_in(n, alloc.clone());
for i in 0..n {
index.push(i);
}
index.shuffle(&mut rng);

for i in index {
v[i] = Vec::new_in(alloc.clone());
}
drop(v);
}

fn btree_map(n: usize, alloc: &(impl Allocator + Clone)) {
let mut rng = SmallRng::seed_from_u64(0xdead_beef);
let mut m = BTreeMap::new_in(alloc.clone());
for _ in 0..n {
if rng.next_u32() % 5 == 0 && !m.is_empty() {
m.pop_first();
} else {
let value = rng.next_u32();
let mut key = Vec::new_in(alloc.clone());
write!(&mut key, "key_{value}").unwrap();
m.insert(key, value);
}
}
m.clear();
drop(m);
}

fn bench(c: &mut Criterion, alloc_name: &str, alloc: impl Allocator + Clone) {
let mut g = c.benchmark_group(alloc_name);
g.bench_function("vec_push_3M", |b| {
b.iter(|| vec_push(black_box(3_000_000), &alloc));
});
g.sample_size(10);
g.bench_function("vec_rand_free_25K_64", |b| {
b.iter(|| vec_rand_free(black_box(25_000), black_box(64), &alloc));
});
g.bench_function("vec_rand_free_7500_520", |b| {
b.iter(|| vec_rand_free(black_box(7_500), black_box(520), &alloc));
});
g.bench_function("btree_map_50K", |b| {
b.iter(|| btree_map(black_box(50_000), &alloc));
});
}

fn criterion_benchmark(c: &mut Criterion) {
let mut pool = MemoryPool::new(POOL_SIZE);
bench(c, "system", std::alloc::System);
bench(
c,
"tlsf",
AllocatorRc::new(TlsfByteAllocator::new(), pool.as_slice()),
);
bench(
c,
"slab",
AllocatorRc::new(SlabByteAllocator::new(), pool.as_slice()),
);
bench(
c,
"buddy",
AllocatorRc::new(BuddyByteAllocator::new(), pool.as_slice()),
);
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
25 changes: 25 additions & 0 deletions benches/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::alloc::Layout;
use std::ptr::NonNull;

pub struct MemoryPool {
ptr: NonNull<u8>,
layout: Layout,
}

impl MemoryPool {
pub fn new(size: usize) -> Self {
let layout = Layout::from_size_align(size, 4096).unwrap();
let ptr = NonNull::new(unsafe { std::alloc::alloc_zeroed(layout) }).unwrap();
Self { ptr, layout }
}

pub fn as_slice(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.layout.size()) }
}
}

impl Drop for MemoryPool {
fn drop(&mut self) {
unsafe { std::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
}
}
95 changes: 95 additions & 0 deletions src/bitmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! Bitmap allocation in page-granularity.
//!
//! TODO: adaptive size
use bitmap_allocator::BitAlloc;

use crate::{AllocError, AllocResult, BaseAllocator, PageAllocator};

// Support max 1M * 4096 = 4GB memory.
type BitAllocUsed = bitmap_allocator::BitAlloc1M;

/// A page-granularity memory allocator based on the [bitmap_allocator].
///
/// It internally uses a bitmap, each bit indicates whether a page has been
/// allocated.
///
/// The `PAGE_SIZE` must be a power of two.
///
/// [bitmap_allocator]: https://github.com/rcore-os/bitmap-allocator
pub struct BitmapPageAllocator<const PAGE_SIZE: usize> {
base: usize,
total_pages: usize,
used_pages: usize,
inner: BitAllocUsed,
}

impl<const PAGE_SIZE: usize> BitmapPageAllocator<PAGE_SIZE> {
/// Creates a new empty `BitmapPageAllocator`.
pub const fn new() -> Self {
Self {
base: 0,
total_pages: 0,
used_pages: 0,
inner: BitAllocUsed::DEFAULT,
}
}
}

impl<const PAGE_SIZE: usize> BaseAllocator for BitmapPageAllocator<PAGE_SIZE> {
fn init(&mut self, start: usize, size: usize) {
assert!(PAGE_SIZE.is_power_of_two());
let end = super::align_down(start + size, PAGE_SIZE);
let start = super::align_up(start, PAGE_SIZE);
self.base = start;
self.total_pages = (end - start) / PAGE_SIZE;
self.inner.insert(0..self.total_pages);
}

fn add_memory(&mut self, _start: usize, _size: usize) -> AllocResult {
Err(AllocError::NoMemory) // unsupported
}
}

impl<const PAGE_SIZE: usize> PageAllocator for BitmapPageAllocator<PAGE_SIZE> {
const PAGE_SIZE: usize = PAGE_SIZE;

fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
if align_pow2 % PAGE_SIZE != 0 {
return Err(AllocError::InvalidParam);
}
let align_pow2 = align_pow2 / PAGE_SIZE;
if !align_pow2.is_power_of_two() {
return Err(AllocError::InvalidParam);
}
let align_log2 = align_pow2.trailing_zeros() as usize;
match num_pages.cmp(&1) {
core::cmp::Ordering::Equal => self.inner.alloc().map(|idx| idx * PAGE_SIZE + self.base),
core::cmp::Ordering::Greater => self
.inner
.alloc_contiguous(num_pages, align_log2)
.map(|idx| idx * PAGE_SIZE + self.base),
_ => return Err(AllocError::InvalidParam),
}
.ok_or(AllocError::NoMemory)
.inspect(|_| self.used_pages += num_pages)
}

fn dealloc_pages(&mut self, pos: usize, num_pages: usize) {
// TODO: not decrease `used_pages` if deallocation failed
self.used_pages -= num_pages;
self.inner.dealloc((pos - self.base) / PAGE_SIZE)
}

fn total_pages(&self) -> usize {
self.total_pages
}

fn used_pages(&self) -> usize {
self.used_pages
}

fn available_pages(&self) -> usize {
self.total_pages - self.used_pages
}
}
58 changes: 58 additions & 0 deletions src/buddy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Buddy memory allocation.
//!
//! TODO: more efficient
use buddy_system_allocator::Heap;
use core::alloc::Layout;
use core::ptr::NonNull;

use crate::{AllocError, AllocResult, BaseAllocator, ByteAllocator};

/// A byte-granularity memory allocator based on the [buddy_system_allocator].
///
/// [buddy_system_allocator]: https://docs.rs/buddy_system_allocator/latest/buddy_system_allocator/
pub struct BuddyByteAllocator {
inner: Heap<32>,
}

impl BuddyByteAllocator {
/// Creates a new empty `BuddyByteAllocator`.
pub const fn new() -> Self {
Self {
inner: Heap::<32>::new(),
}
}
}

impl BaseAllocator for BuddyByteAllocator {
fn init(&mut self, start: usize, size: usize) {
unsafe { self.inner.init(start, size) };
}

fn add_memory(&mut self, start: usize, size: usize) -> AllocResult {
unsafe { self.inner.add_to_heap(start, start + size) };
Ok(())
}
}

impl ByteAllocator for BuddyByteAllocator {
fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
self.inner.alloc(layout).map_err(|_| AllocError::NoMemory)
}

fn dealloc(&mut self, pos: NonNull<u8>, layout: Layout) {
self.inner.dealloc(pos, layout)
}

fn total_bytes(&self) -> usize {
self.inner.stats_total_bytes()
}

fn used_bytes(&self) -> usize {
self.inner.stats_alloc_actual()
}

fn available_bytes(&self) -> usize {
self.inner.stats_total_bytes() - self.inner.stats_alloc_actual()
}
}
Loading

0 comments on commit 0dc4c1d

Please sign in to comment.