Skip to content

Commit 037dad5

Browse files
authoredJul 14, 2024··
fix(allocator): Fix allocator & add benchmark (#9234)
**Related issue:** - This PR is a part of #9230.
1 parent 26c5519 commit 037dad5

File tree

9 files changed

+226
-35
lines changed

9 files changed

+226
-35
lines changed
 

‎.github/workflows/CI.yml

+1
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ jobs:
330330
tool: cargo-hack@0.5.29
331331

332332
- name: Check compilation
333+
if: matrix.settings.os == 'ubuntu-latest'
333334
run: |
334335
./scripts/github/run-cargo-hack.sh ${{ matrix.settings.crate }}
335336

‎Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/swc_allocator/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,15 @@ rkyv = { workspace = true, optional = true }
2424
scoped-tls = { workspace = true }
2525
serde = { workspace = true, optional = true }
2626
serde_derive = { workspace = true, optional = true }
27+
28+
29+
[dev-dependencies]
30+
criterion = { workspace = true }
31+
32+
codspeed-criterion-compat = { workspace = true }
33+
swc_malloc = { version = "0.5.10", path = "../swc_malloc" }
34+
35+
36+
[[bench]]
37+
harness = false
38+
name = "bench"

‎crates/swc_allocator/benches/bench.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
extern crate swc_malloc;
2+
3+
use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Bencher, Criterion};
4+
use swc_allocator::{FastAlloc, MemorySpace};
5+
6+
fn bench_alloc(c: &mut Criterion) {
7+
fn direct_alloc_std(b: &mut Bencher, times: usize) {
8+
b.iter(|| {
9+
let mut buf = std::vec::Vec::new();
10+
for i in 0..times {
11+
let item: std::boxed::Box<usize> = black_box(std::boxed::Box::new(black_box(i)));
12+
buf.push(item);
13+
}
14+
})
15+
}
16+
17+
fn direct_alloc_no_scope(b: &mut Bencher, times: usize) {
18+
b.iter(|| {
19+
let mut vec = swc_allocator::vec::Vec::new();
20+
for i in 0..times {
21+
let item: swc_allocator::boxed::Box<usize> =
22+
black_box(swc_allocator::boxed::Box::new(black_box(i)));
23+
vec.push(item);
24+
}
25+
})
26+
}
27+
28+
fn fast_alloc_no_scope(b: &mut Bencher, times: usize) {
29+
b.iter(|| {
30+
let allocator = FastAlloc::default();
31+
32+
let mut vec = allocator.vec();
33+
for i in 0..times {
34+
let item: swc_allocator::boxed::Box<usize> =
35+
black_box(allocator.alloc(black_box(i)));
36+
vec.push(item);
37+
}
38+
})
39+
}
40+
41+
fn direct_alloc_scoped(b: &mut Bencher, times: usize) {
42+
b.iter(|| {
43+
let allocator = MemorySpace::default();
44+
45+
allocator.scope(|| {
46+
let mut vec = swc_allocator::vec::Vec::new();
47+
48+
for i in 0..times {
49+
let item: swc_allocator::boxed::Box<usize> =
50+
black_box(swc_allocator::boxed::Box::new(black_box(i)));
51+
vec.push(item);
52+
}
53+
});
54+
})
55+
}
56+
57+
fn fast_alloc_scoped(b: &mut Bencher, times: usize) {
58+
b.iter(|| {
59+
MemorySpace::default().scope(|| {
60+
let allocator = FastAlloc::default();
61+
62+
let mut vec = allocator.vec();
63+
64+
for i in 0..times {
65+
let item: swc_allocator::boxed::Box<usize> =
66+
black_box(allocator.alloc(black_box(i)));
67+
vec.push(item);
68+
}
69+
});
70+
})
71+
}
72+
73+
c.bench_function("common/allocator/alloc/std/1000000", |b| {
74+
direct_alloc_std(b, 1000000)
75+
});
76+
c.bench_function("common/allocator/alloc/no-scope/1000000", |b| {
77+
direct_alloc_no_scope(b, 1000000)
78+
});
79+
c.bench_function("common/allocator/alloc/scoped/1000000", |b| {
80+
direct_alloc_scoped(b, 1000000)
81+
});
82+
83+
c.bench_function("common/allocator/alloc/cached-no-scope/1000000", |b| {
84+
fast_alloc_no_scope(b, 1000000)
85+
});
86+
c.bench_function("common/allocator/alloc/cached-scoped/1000000", |b| {
87+
fast_alloc_scoped(b, 1000000)
88+
});
89+
}
90+
91+
criterion_group!(benches, bench_alloc);
92+
criterion_main!(benches);

‎crates/swc_allocator/src/alloc.rs

+28-23
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,38 @@ use std::{alloc::Layout, ptr::NonNull};
33
use allocator_api2::alloc::Global;
44
use scoped_tls::scoped_thread_local;
55

6-
use crate::Allocator;
6+
use crate::{FastAlloc, MemorySpace};
77

8-
scoped_thread_local!(pub(crate) static ALLOC: Allocator);
8+
scoped_thread_local!(pub(crate) static ALLOC: MemorySpace);
99

10-
#[derive(Debug, Clone, Copy, Default)]
11-
pub struct SwcAlloc;
10+
#[derive(Debug, Clone, Copy)]
11+
pub struct SwcAlloc {
12+
pub(crate) is_arena_mode: bool,
13+
}
14+
15+
impl Default for FastAlloc {
16+
fn default() -> Self {
17+
Self {
18+
is_arena_mode: ALLOC.is_set(),
19+
}
20+
}
21+
}
22+
23+
impl Default for SwcAlloc {
24+
fn default() -> Self {
25+
SwcAlloc {
26+
is_arena_mode: ALLOC.is_set(),
27+
}
28+
}
29+
}
1230

1331
impl SwcAlloc {
1432
/// `true` is passed to `f` if the box is allocated with a custom allocator.
1533
fn with_allocator<T>(
1634
&self,
1735
f: impl FnOnce(&dyn allocator_api2::alloc::Allocator, bool) -> T,
1836
) -> T {
19-
if ALLOC.is_set() {
37+
if self.is_arena_mode {
2038
ALLOC.with(|a| {
2139
//
2240
f(&&**a as &dyn allocator_api2::alloc::Allocator, true)
@@ -27,21 +45,8 @@ impl SwcAlloc {
2745
}
2846
}
2947

30-
/// Set the last bit to 1
3148
fn mark_ptr_as_arena_mode(ptr: NonNull<[u8]>) -> NonNull<[u8]> {
32-
let (mut raw_ptr, metadata) = ptr_meta::PtrExt::to_raw_parts(ptr.as_ptr());
33-
34-
raw_ptr = (raw_ptr as usize | 1) as *mut ();
35-
36-
unsafe {
37-
// Safety:
38-
NonNull::new_unchecked(ptr_meta::from_raw_parts_mut(raw_ptr, metadata))
39-
}
40-
}
41-
42-
fn is_ptr_in_arena_mode(ptr: NonNull<u8>) -> bool {
43-
let ptr = ptr.as_ptr() as usize;
44-
ptr & 1 == 1
49+
ptr
4550
}
4651

4752
unsafe impl allocator_api2::alloc::Allocator for SwcAlloc {
@@ -73,7 +78,7 @@ unsafe impl allocator_api2::alloc::Allocator for SwcAlloc {
7378
}
7479

7580
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
76-
if is_ptr_in_arena_mode(ptr) {
81+
if self.is_arena_mode {
7782
debug_assert!(
7883
ALLOC.is_set(),
7984
"Deallocating a pointer allocated with arena mode with a non-arena mode allocator"
@@ -96,7 +101,7 @@ unsafe impl allocator_api2::alloc::Allocator for SwcAlloc {
96101
old_layout: Layout,
97102
new_layout: Layout,
98103
) -> Result<NonNull<[u8]>, allocator_api2::alloc::AllocError> {
99-
if is_ptr_in_arena_mode(ptr) {
104+
if self.is_arena_mode {
100105
debug_assert!(
101106
ALLOC.is_set(),
102107
"Growing a pointer allocated with arena mode with a non-arena mode allocator"
@@ -114,7 +119,7 @@ unsafe impl allocator_api2::alloc::Allocator for SwcAlloc {
114119
old_layout: Layout,
115120
new_layout: Layout,
116121
) -> Result<NonNull<[u8]>, allocator_api2::alloc::AllocError> {
117-
if is_ptr_in_arena_mode(ptr) {
122+
if self.is_arena_mode {
118123
debug_assert!(
119124
ALLOC.is_set(),
120125
"Growing a pointer allocated with arena mode with a non-arena mode allocator"
@@ -132,7 +137,7 @@ unsafe impl allocator_api2::alloc::Allocator for SwcAlloc {
132137
old_layout: Layout,
133138
new_layout: Layout,
134139
) -> Result<NonNull<[u8]>, allocator_api2::alloc::AllocError> {
135-
if is_ptr_in_arena_mode(ptr) {
140+
if self.is_arena_mode {
136141
debug_assert!(
137142
ALLOC.is_set(),
138143
"Shrinking a pointer allocated with arena mode with a non-arena mode allocator"

‎crates/swc_allocator/src/boxed/mod.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
pin::Pin,
88
};
99

10-
use crate::alloc::SwcAlloc;
10+
use crate::{alloc::SwcAlloc, FastAlloc};
1111

1212
#[cfg(feature = "rkyv")]
1313
mod rkyv;
@@ -54,7 +54,10 @@ impl<T> Box<T> {
5454
/// See [`std::boxed::Box::new`].
5555
#[inline(always)]
5656
pub fn new(value: T) -> Self {
57-
Self(allocator_api2::boxed::Box::new_in(value, SwcAlloc))
57+
Self(allocator_api2::boxed::Box::new_in(
58+
value,
59+
SwcAlloc::default(),
60+
))
5861
}
5962

6063
/// Moves the value out of the box.
@@ -106,7 +109,10 @@ impl<T: ?Sized> Box<T> {
106109
/// [memory layout]: self#memory-layout
107110
/// [`Layout`]: crate::Layout
108111
pub unsafe fn from_raw(raw: *mut T) -> Self {
109-
Self(allocator_api2::boxed::Box::from_raw_in(raw, SwcAlloc))
112+
Self(allocator_api2::boxed::Box::from_raw_in(
113+
raw,
114+
SwcAlloc::default(),
115+
))
110116
}
111117

112118
/// Consumes the `Box`, returning a wrapped raw pointer.
@@ -621,3 +627,9 @@ where
621627
self.0.len()
622628
}
623629
}
630+
631+
impl FastAlloc {
632+
pub fn alloc<T>(self, t: T) -> Box<T> {
633+
Box(allocator_api2::boxed::Box::new_in(t, self.swc_alloc()))
634+
}
635+
}

‎crates/swc_allocator/src/lib.rs

+19-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
#![allow(clippy::needless_doctest_main)]
66

7+
use alloc::SwcAlloc;
78
use std::ops::{Deref, DerefMut};
89

910
use bumpalo::Bump;
@@ -14,12 +15,25 @@ mod alloc;
1415
pub mod boxed;
1516
pub mod vec;
1617

18+
#[derive(Debug, Clone, Copy)]
19+
pub struct FastAlloc {
20+
is_arena_mode: bool,
21+
}
22+
23+
impl FastAlloc {
24+
fn swc_alloc(self) -> SwcAlloc {
25+
SwcAlloc {
26+
is_arena_mode: self.is_arena_mode,
27+
}
28+
}
29+
}
30+
1731
#[derive(Default)]
18-
pub struct Allocator {
32+
pub struct MemorySpace {
1933
alloc: Bump,
2034
}
2135

22-
impl Allocator {
36+
impl MemorySpace {
2337
/// Invokes `f` in a scope where the allocations are done in this allocator.
2438
#[inline(always)]
2539
pub fn scope<F, R>(&self, f: F) -> R
@@ -30,21 +44,21 @@ impl Allocator {
3044
}
3145
}
3246

33-
impl From<Bump> for Allocator {
47+
impl From<Bump> for MemorySpace {
3448
fn from(alloc: Bump) -> Self {
3549
Self { alloc }
3650
}
3751
}
3852

39-
impl Deref for Allocator {
53+
impl Deref for MemorySpace {
4054
type Target = Bump;
4155

4256
fn deref(&self) -> &Bump {
4357
&self.alloc
4458
}
4559
}
4660

47-
impl DerefMut for Allocator {
61+
impl DerefMut for MemorySpace {
4862
fn deref_mut(&mut self) -> &mut Bump {
4963
&mut self.alloc
5064
}

‎crates/swc_allocator/src/vec/mod.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut};
33
#[cfg(feature = "rkyv")]
44
mod rkyv;
55

6-
use crate::{alloc::SwcAlloc, boxed::Box};
6+
use crate::{alloc::SwcAlloc, boxed::Box, FastAlloc};
77

88
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
99
#[repr(transparent)]
@@ -20,7 +20,8 @@ impl<T> Vec<T> {
2020

2121
pub fn with_capacity(capacity: usize) -> Self {
2222
Self(allocator_api2::vec::Vec::with_capacity_in(
23-
capacity, SwcAlloc,
23+
capacity,
24+
SwcAlloc::default(),
2425
))
2526
}
2627

@@ -163,7 +164,10 @@ impl<T> Vec<T> {
163164
/// ```
164165
pub unsafe fn from_raw_parts(ptr: *mut T, length: usize, capacity: usize) -> Self {
165166
Self(allocator_api2::vec::Vec::from_raw_parts_in(
166-
ptr, length, capacity, SwcAlloc,
167+
ptr,
168+
length,
169+
capacity,
170+
SwcAlloc::default(),
167171
))
168172
}
169173
}
@@ -184,7 +188,7 @@ impl<T> DerefMut for Vec<T> {
184188

185189
impl<T> Default for Vec<T> {
186190
fn default() -> Self {
187-
Self(allocator_api2::vec::Vec::new_in(SwcAlloc))
191+
Self(allocator_api2::vec::Vec::new_in(SwcAlloc::default()))
188192
}
189193
}
190194

@@ -239,3 +243,16 @@ impl<T> Extend<T> for Vec<T> {
239243
self.0.extend(iter)
240244
}
241245
}
246+
247+
impl FastAlloc {
248+
pub fn vec<T>(self) -> Vec<T> {
249+
Vec(allocator_api2::vec::Vec::new_in(self.swc_alloc()))
250+
}
251+
252+
pub fn vec_with_capacity<T>(self, capacity: usize) -> Vec<T> {
253+
Vec(allocator_api2::vec::Vec::with_capacity_in(
254+
capacity,
255+
self.swc_alloc(),
256+
))
257+
}
258+
}

‎crates/swc_allocator/tests/apis.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1 +1,36 @@
1+
use criterion::black_box;
2+
use swc_allocator::MemorySpace;
13

4+
#[test]
5+
fn direct_alloc_std() {
6+
let mut buf = std::vec::Vec::new();
7+
for i in 0..1000 {
8+
let item: std::boxed::Box<usize> = black_box(std::boxed::Box::new(black_box(i)));
9+
buf.push(item);
10+
}
11+
}
12+
13+
#[test]
14+
fn direct_alloc_no_scope() {
15+
let mut vec = swc_allocator::vec::Vec::new();
16+
for i in 0..1000 {
17+
let item: swc_allocator::boxed::Box<usize> =
18+
black_box(swc_allocator::boxed::Box::new(black_box(i)));
19+
vec.push(item);
20+
}
21+
}
22+
23+
#[test]
24+
fn direct_alloc_in_scope() {
25+
let allocator = MemorySpace::default();
26+
27+
allocator.scope(|| {
28+
let mut vec = swc_allocator::vec::Vec::new();
29+
30+
for i in 0..1000 {
31+
let item: swc_allocator::boxed::Box<usize> =
32+
black_box(swc_allocator::boxed::Box::new(black_box(i)));
33+
vec.push(item);
34+
}
35+
});
36+
}

0 commit comments

Comments
 (0)
Please sign in to comment.