├── img ├── numanji.jpg └── numanji-square.png ├── src ├── nonnuma_allocator.rs ├── autoselect.rs ├── lib.rs └── numa_aware_allocator.rs ├── examples └── autoselect.rs ├── .github ├── workflows │ ├── clippy.yml │ └── ci.yml └── FUNDING.yml ├── .gitignore ├── README.md ├── Cargo.toml └── benches └── long_cons_then_map.rs /img/numanji.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastion-rs/numanji/HEAD/img/numanji.jpg -------------------------------------------------------------------------------- /img/numanji-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastion-rs/numanji/HEAD/img/numanji-square.png -------------------------------------------------------------------------------- /src/nonnuma_allocator.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! nonnuma_allocator { 3 | () => { 4 | #[allow(missing_docs)] 5 | #[global_allocator] 6 | pub static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /examples/autoselect.rs: -------------------------------------------------------------------------------- 1 | #![feature(allocator_api)] 2 | #![feature(nonnull_slice_from_raw_parts)] 3 | 4 | // Allocator generator macro 5 | use numanji::*; 6 | 7 | // Do autoselect for allocator 8 | autoselect!(); 9 | 10 | fn main() { 11 | // Allocated by Numanji based on your Numa availability on your system. 12 | let _vec = Vec::::with_capacity(1234); 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | 3 | name: Clippy Check 4 | jobs: 5 | clippy_check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v1 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | toolchain: nightly 12 | components: clippy 13 | override: true 14 | - uses: actions-rs/clippy-check@v1 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/autoselect.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(target_os = "android", target_os = "linux")))] 2 | #[macro_export] 3 | macro_rules! autoselect { 4 | () => { 5 | use numanji::nonnuma_allocator; 6 | nonnuma_allocator!(); 7 | }; 8 | } 9 | 10 | #[cfg(any(target_os = "android", target_os = "linux"))] 11 | #[macro_export] 12 | macro_rules! autoselect { 13 | () => { 14 | use numanji::numa_aware_allocator; 15 | numa_aware_allocator!(); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | **/target 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | /target 14 | **/*.rs.bk 15 | 16 | *.bc 17 | 18 | bcs 19 | 20 | # Intellij stuff 21 | .idea/ 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: bastion 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Local-affinity first NUMA-aware allocator with optional fallback. 6 | 7 | This crate supplies NUMA-aware local policy enabled allocation. 8 | 9 | ### When using `autoselect` 10 | Fallback system is triggered with `autoselect`. 11 | If system is not supporting NUMA-aware allocation it falls back to `Jemalloc`. 12 | 13 | ### When using `NUMA-aware` 14 | If autoselect is not used, `memmap` fallback will be triggered with 15 | default system page size and it will be used as allocator. 16 | 17 | ```rust 18 | // Allocator generator macro 19 | use numanji::*; 20 | 21 | // Do autoselect for allocator 22 | autoselect!(); 23 | 24 | fn main() { 25 | // Allocated by Numanji based on your Numa availability on your system. 26 | let _vec = Vec::::with_capacity(1234); 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "numanji" 3 | version = "0.1.6-alpha.0" 4 | description = "Local-affinity first NUMA-aware allocator with optional fallback" 5 | authors = ["Mahmut Bulut "] 6 | keywords = ["numa", "numa-aware", "allocator", "memory"] 7 | categories = ["memory-management", "caching"] 8 | homepage = "https://github.com/bastion-rs/numanji" 9 | repository = "https://github.com/bastion-rs/numanji" 10 | documentation = "https://docs.rs/numanji" 11 | edition = "2018" 12 | readme = "README.md" 13 | license = "Apache-2.0/MIT" 14 | exclude = [ 15 | ".github/*", 16 | "examples/*", 17 | "graphstore/*", 18 | "tests/*", 19 | "img/*", 20 | "ci/*", 21 | "benches/*", 22 | "doc/*", 23 | "*.png", 24 | "*.dot", 25 | "*.yml", 26 | "*.toml", 27 | "*.md" 28 | ] 29 | 30 | [badges] 31 | maintenance = { status = "actively-developed" } 32 | 33 | [dependencies] 34 | lazy_static = "^1.4" 35 | allocator-suite = "0.1.6" 36 | 37 | [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] 38 | jemallocator = "^0.3" 39 | 40 | [dev-dependencies] 41 | # long_cons_then_map bench 42 | rand = "0.7" 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! [![Numanji](https://raw.githubusercontent.com/bastion-rs/numanji/master/img/numanji.jpg)](https://github.com/bastion-rs/numanji) 3 | //! 4 | //! Local-affinity first NUMA-aware allocator with optional fallback. 5 | //! 6 | //! This crate supplies NUMA-aware local policy enabled allocation. 7 | //! 8 | //! ### When using `autoselect` 9 | //! Fallback system is triggered with `autoselect`. 10 | //! If system is not supporting NUMA-aware allocation it falls back to `Jemalloc`. 11 | //! 12 | //! ### When using `NUMA-aware` 13 | //! If autoselect is not used, `memmap` fallback will be triggered with 14 | //! default system page size and it will be used as allocator. 15 | //! 16 | //! # Examples 17 | //! 18 | //! ```rust 19 | //! // Allocator generator macro 20 | //! #![feature(allocator_api)] 21 | //! #![feature(nonnull_slice_from_raw_parts)] 22 | //! use numanji::*; 23 | //! 24 | //! // Do autoselect for allocator 25 | //! autoselect!(); 26 | //! 27 | //! // Allocated by Numanji based on your Numa availability on your system. 28 | //! let _vec = Vec::::with_capacity(1234); 29 | //! ``` 30 | //! 31 | 32 | #![feature(allocator_api)] 33 | #![feature(nonnull_slice_from_raw_parts)] 34 | 35 | mod autoselect; 36 | mod nonnuma_allocator; 37 | mod numa_aware_allocator; 38 | 39 | pub mod prelude { 40 | pub use super::autoselect; 41 | pub use super::nonnuma_allocator; 42 | pub use super::numa_aware_allocator; 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | toolchain: 11 | - x86_64-unknown-linux-gnu 12 | - x86_64-apple-darwin 13 | version: 14 | - nightly 15 | include: 16 | - toolchain: x86_64-unknown-linux-gnu 17 | os: ubuntu-latest 18 | - toolchain: x86_64-apple-darwin 19 | os: macOS-latest 20 | 21 | name: ${{ matrix.version }} - ${{ matrix.toolchain }} 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - uses: actions/checkout@master 26 | 27 | - name: Install ${{ matrix.version }} 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: ${{ matrix.version }}-${{ matrix.toolchain }} 31 | default: true 32 | 33 | - name: check nightly 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: check 37 | args: --all --benches --bins --examples --tests 38 | 39 | - name: tests nightly 40 | uses: actions-rs/cargo@v1 41 | if: toolchain == 'x86_64-unknown-linux-gnu' 42 | with: 43 | command: test 44 | args: --all --all-targets --all-features 45 | 46 | check_fmt_and_docs: 47 | name: Checking fmt and docs 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@master 51 | 52 | - name: Setup 53 | uses: actions-rs/toolchain@v1 54 | with: 55 | toolchain: nightly 56 | default: true 57 | components: rustfmt 58 | 59 | - name: fmt 60 | run: cargo fmt --all -- --check 61 | 62 | - name: doc 63 | run: cargo doc 64 | -------------------------------------------------------------------------------- /src/numa_aware_allocator.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! numa_aware_allocator { 3 | () => { 4 | // General imports 5 | use allocator_suite::adaptors::prelude::*; 6 | use core::ptr::NonNull; 7 | use std::alloc::{System, GlobalAlloc, AllocRef, Layout, AllocErr}; 8 | use allocator_suite::memory_sources::mmap::memory_map_source::MemoryMapSource; 9 | use allocator_suite::extensions::usize_ext::UsizeExt; 10 | use allocator_suite::allocators::allocator::Allocator; 11 | use allocator_suite::memory_address::MemoryAddress; 12 | use allocator_suite::allocators::memory_map_allocator::MemoryMapAllocator; 13 | 14 | use lazy_static::*; 15 | 16 | #[allow(missing_docs)] 17 | pub fn allocator_instance() -> &'static AllocatorAdaptor<'static, MemoryMapAllocator> { 18 | lazy_static! { 19 | static ref MMAP_ALLOC: MemoryMapAllocator = { 20 | #[cfg(any(target_os = "android", target_os = "linux"))] { 21 | use allocator_suite::memory_sources::mmap::numa::numa_settings::NumaSettings; 22 | use allocator_suite::memory_sources::mmap::numa::numa_allocation_policy::NumaAllocationPolicy; 23 | 24 | let numa_settings = NumaSettings::new(NumaAllocationPolicy::Local, false); 25 | let mmap = MemoryMapSource::with_numa_settings(numa_settings); 26 | MemoryMapAllocator(mmap) 27 | } 28 | 29 | #[cfg(not(any(target_os = "android", target_os = "linux")))] { 30 | let mmap = MemoryMapSource::default(); 31 | MemoryMapAllocator(mmap) 32 | } 33 | }; 34 | 35 | 36 | static ref MMAP_ADAPTER: AllocatorAdaptor<'static, MemoryMapAllocator> = { 37 | MMAP_ALLOC.adapt() 38 | }; 39 | } 40 | 41 | &*MMAP_ADAPTER 42 | } 43 | 44 | #[allow(missing_docs)] 45 | #[derive(Debug, Copy, Clone)] 46 | pub struct NumaAllocator; 47 | 48 | #[allow(missing_docs)] 49 | #[global_allocator] 50 | pub static GLOBAL: NumaAllocator = NumaAllocator; 51 | 52 | unsafe impl Sync for NumaAllocator {} 53 | 54 | unsafe impl GlobalAlloc for NumaAllocator { 55 | #[inline(always)] 56 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 57 | { 58 | allocator_instance().global_alloc_alloc(layout) 59 | } 60 | 61 | #[inline(always)] 62 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) 63 | { 64 | allocator_instance().global_alloc_dealloc(ptr, layout) 65 | } 66 | 67 | #[inline(always)] 68 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 69 | { 70 | allocator_instance().global_alloc_alloc_zeroed(layout) 71 | } 72 | 73 | #[inline(always)] 74 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 75 | { 76 | allocator_instance().global_alloc_realloc(ptr, layout, new_size) 77 | } 78 | } 79 | 80 | unsafe impl AllocRef for NumaAllocator { 81 | #[inline(always)] 82 | fn alloc(&mut self, layout: Layout) -> Result, AllocErr> 83 | { 84 | let size = layout.size(); 85 | let ptr = unsafe { allocator_instance().alloc_alloc_zeroed(layout) }?; 86 | Ok(NonNull::slice_from_raw_parts(ptr, size)) 87 | } 88 | 89 | #[inline(always)] 90 | unsafe fn dealloc(&mut self, ptr: MemoryAddress, layout: Layout) 91 | { 92 | allocator_instance().alloc_dealloc(ptr, layout) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /benches/long_cons_then_map.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | // https://github.com/bastion-rs/numanji/issues/1 4 | 5 | use std::fmt::{Debug, Error, Formatter}; 6 | use std::sync::Arc; 7 | 8 | enum ArcList { 9 | Cons(T, Arc), 10 | Nil, 11 | } 12 | 13 | impl ArcList { 14 | fn is_nil(&self) -> bool { 15 | match self { 16 | Self::Nil => true, 17 | _ => false, 18 | } 19 | } 20 | } 21 | 22 | impl Debug for ArcList { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 24 | match self { 25 | Self::Nil => Ok(()), 26 | Self::Cons(e, t) if t.is_nil() => write!(f, "{:?}", e), 27 | Self::Cons(e, t) => write!(f, "{:?}, {:?}", e, *(*t)), 28 | } 29 | } 30 | } 31 | type Ptr = Arc>; 32 | 33 | fn cons(t: T, list: Ptr) -> Ptr { 34 | Arc::new(ArcList::Cons(t, list.clone())) 35 | } 36 | 37 | fn count_inner(acc: usize, list: Ptr) -> usize { 38 | match &*list { 39 | ArcList::Nil => acc, 40 | ArcList::Cons(_, t) => count_inner(acc + 1, t.clone()), 41 | } 42 | } 43 | 44 | fn count(list: Ptr) -> usize { 45 | count_inner(0, list) 46 | } 47 | 48 | fn map(f: fn(&T) -> U, list: Ptr) -> Ptr { 49 | match &*list { 50 | ArcList::Nil => Arc::new(ArcList::Nil), 51 | ArcList::Cons(x, t) => Arc::new(ArcList::Cons(f(x), map(f, t.clone()))), 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod list_bench { 57 | extern crate test; 58 | use super::*; 59 | use rand::Rng; 60 | use std::sync::Arc; 61 | use test::Bencher; 62 | const SCALE: usize = 10000; 63 | #[bench] 64 | fn long_cons_then_count(bencher: &mut Bencher) { 65 | bencher.iter(|| { 66 | let mut rng = rand::thread_rng(); 67 | let mut a = Arc::new(ArcList::Nil); 68 | for _ in 0..SCALE { 69 | a = cons(rng.gen::(), a); 70 | } 71 | assert_eq!(count(a), SCALE) 72 | }); 73 | } 74 | 75 | #[bench] 76 | fn long_cons_then_map(bencher: &mut Bencher) { 77 | bencher.iter(|| { 78 | let mut rng = rand::thread_rng(); 79 | let mut a = Arc::new(ArcList::Nil); 80 | for _ in 0..SCALE { 81 | a = cons(rng.gen::(), a); 82 | } 83 | map(|x| x + 1, a); 84 | }); 85 | } 86 | 87 | #[bench] 88 | fn long_cons_then_count_in_multi_threads(bencher: &mut Bencher) { 89 | bencher.iter(|| { 90 | let mut handles = Vec::new(); 91 | for _ in 0..6 { 92 | handles.push( 93 | std::thread::Builder::new() 94 | .stack_size(512 * 1024 * 1024) 95 | .spawn(|| { 96 | let mut rng = rand::thread_rng(); 97 | let mut a = Arc::new(ArcList::Nil); 98 | for _ in 0..SCALE { 99 | a = cons(rng.gen::(), a); 100 | } 101 | assert_eq!(count(a), SCALE) 102 | }) 103 | .unwrap(), 104 | ); 105 | } 106 | for i in handles { 107 | i.join().unwrap(); 108 | } 109 | }); 110 | } 111 | 112 | #[bench] 113 | fn long_cons_then_map_across_multi_threads(bencher: &mut Bencher) { 114 | bencher.iter(|| { 115 | let mut rng = rand::thread_rng(); 116 | let mut handles = Vec::new(); 117 | let mut a = Arc::new(ArcList::Nil); 118 | for _ in 0..SCALE { 119 | a = cons(rng.gen::(), a); 120 | } 121 | for _ in 0..6 { 122 | let a = a.clone(); 123 | handles.push( 124 | std::thread::Builder::new() 125 | .stack_size(512 * 1024 * 1024) 126 | .spawn(move || { 127 | map(|x| x + 1, a); 128 | }) 129 | .unwrap(), 130 | ); 131 | } 132 | for i in handles { 133 | i.join().unwrap(); 134 | } 135 | }); 136 | } 137 | } 138 | --------------------------------------------------------------------------------