Skip to content

Commit 6ecfd8d

Browse files
perf: use HashMap to store alloc sizes instead of header (#602)
1 parent a0c5d75 commit 6ecfd8d

4 files changed

Lines changed: 48 additions & 60 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hyperion/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ geometry = {workspace = true}
2525
bitfield-struct = {workspace = true}
2626
simd-utils = {workspace = true}
2727
bitvec = {workspace = true}
28+
papaya = {workspace = true}
2829
bumpalo = {workspace = true}
2930
bvh-region = {workspace = true}
3031
bytemuck = {workspace = true}

crates/hyperion/src/alloc.rs

Lines changed: 38 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
alloc::{Layout, alloc, dealloc, realloc},
33
ptr::null_mut,
4+
sync::OnceLock,
45
};
56

67
use flecs_ecs::sys::{
@@ -9,18 +10,23 @@ use flecs_ecs::sys::{
910

1011
const ALIGNMENT: usize = 64;
1112

12-
// Store size in a prefix, perfectly aligned
13-
#[repr(C, align(64))] // Ensure the header itself is aligned
14-
struct AllocHeader {
15-
size: usize,
13+
// Global size tracker using papaya HashMap
14+
static ALLOC_SIZES: OnceLock<papaya::HashMap<usize, usize>> = OnceLock::new();
15+
16+
fn init_size_map() {
17+
ALLOC_SIZES.get_or_init(papaya::HashMap::new);
18+
}
19+
20+
fn get_size_map() -> &'static papaya::HashMap<usize, usize> {
21+
unsafe { ALLOC_SIZES.get().unwrap_unchecked() }
1622
}
1723

1824
unsafe extern "C-unwind" fn aligned_malloc(size: ecs_size_t) -> *mut core::ffi::c_void {
1925
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
20-
let total_size = size as usize + size_of::<AllocHeader>();
26+
let size = size as usize;
2127

2228
// Allocate with our desired alignment
23-
let Ok(layout) = Layout::from_size_align(total_size, ALIGNMENT) else {
29+
let Ok(layout) = Layout::from_size_align(size, ALIGNMENT) else {
2430
return null_mut();
2531
};
2632

@@ -29,26 +35,16 @@ unsafe extern "C-unwind" fn aligned_malloc(size: ecs_size_t) -> *mut core::ffi::
2935
return null_mut();
3036
}
3137

32-
// Write the header
33-
#[allow(clippy::cast_ptr_alignment)]
34-
let header = ptr.cast::<AllocHeader>();
38+
// Store the size in our global map
39+
get_size_map().pin().insert(ptr as usize, size);
3540

36-
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
37-
unsafe {
38-
(*header).size = size as usize;
39-
};
40-
41-
// Return pointer after the header
42-
unsafe {
43-
ptr.add(size_of::<AllocHeader>())
44-
.cast::<core::ffi::c_void>()
45-
}
41+
ptr.cast::<core::ffi::c_void>()
4642
}
4743

4844
unsafe extern "C-unwind" fn aligned_calloc(size: ecs_size_t) -> *mut core::ffi::c_void {
4945
let ptr = unsafe { aligned_malloc(size) };
5046
if !ptr.is_null() {
51-
// Zero only the user data portion, header already contains size
47+
// Zero the entire allocation
5248
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
5349
unsafe {
5450
std::ptr::write_bytes(ptr, 0, size as usize);
@@ -65,68 +61,50 @@ unsafe extern "C-unwind" fn aligned_realloc(
6561
return unsafe { aligned_malloc(new_size) };
6662
}
6763

68-
// Get the header pointer from the user pointer
69-
#[allow(clippy::cast_ptr_alignment)]
70-
let header_ptr = unsafe {
71-
ptr.cast::<u8>()
72-
.sub(size_of::<AllocHeader>())
73-
.cast::<AllocHeader>()
74-
};
75-
let old_size = unsafe { (*header_ptr).size };
76-
7764
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
78-
let total_new_size = new_size as usize + size_of::<AllocHeader>();
65+
let new_size = new_size as usize;
7966

80-
// Reallocate with the total size
81-
let layout = unsafe {
82-
Layout::from_size_align_unchecked(old_size + size_of::<AllocHeader>(), ALIGNMENT)
83-
};
67+
let size_map = get_size_map().pin();
68+
69+
// Get the old size from our map
70+
let old_size = size_map.get(&(ptr as usize)).copied().unwrap_or(0);
8471

85-
let new_ptr = unsafe { realloc(header_ptr.cast::<u8>(), layout, total_new_size) };
72+
// Create layout for reallocation
73+
let layout = unsafe { Layout::from_size_align_unchecked(old_size, ALIGNMENT) };
8674

75+
let new_ptr = unsafe { realloc(ptr.cast::<u8>(), layout, new_size) };
8776
if new_ptr.is_null() {
8877
return null_mut();
8978
}
9079

91-
// Update size in header
92-
#[allow(clippy::cast_ptr_alignment)]
93-
let new_header = new_ptr.cast::<AllocHeader>();
94-
95-
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
96-
unsafe {
97-
(*new_header).size = new_size as usize;
98-
};
80+
// Update size in map
81+
size_map.remove(&(ptr as usize));
82+
size_map.insert(new_ptr as usize, new_size);
9983

100-
// Return pointer after header
101-
unsafe {
102-
new_ptr
103-
.add(size_of::<AllocHeader>())
104-
.cast::<core::ffi::c_void>()
105-
}
84+
new_ptr.cast::<core::ffi::c_void>()
10685
}
10786

10887
unsafe extern "C-unwind" fn aligned_free(ptr: *mut core::ffi::c_void) {
10988
if !ptr.is_null() {
110-
// Get the header pointer
111-
let header_ptr = unsafe { ptr.cast::<u8>().sub(size_of::<AllocHeader>()) };
112-
113-
#[allow(clippy::cast_ptr_alignment)]
114-
let header = header_ptr.cast::<AllocHeader>();
115-
let total_size = unsafe { (*header).size + size_of::<AllocHeader>() };
116-
117-
// Deallocate the entire block
118-
let layout = unsafe { Layout::from_size_align_unchecked(total_size, ALIGNMENT) };
119-
unsafe { dealloc(header_ptr, layout) };
89+
let size_map = get_size_map().pin();
90+
// Get the size from our map
91+
if let Some(size) = size_map.remove(&(ptr as usize)) {
92+
// Deallocate the block
93+
let layout = unsafe { Layout::from_size_align_unchecked(*size, ALIGNMENT) };
94+
unsafe { dealloc(ptr.cast::<u8>(), layout) };
95+
}
12096
}
12197
}
12298

123-
// Setup function remains the same
12499
pub fn setup_custom_allocators() -> (
125100
ecs_os_api_malloc_t,
126101
ecs_os_api_calloc_t,
127102
ecs_os_api_realloc_t,
128103
ecs_os_api_free_t,
129104
) {
105+
// Initialize the global size map if not already initialized
106+
init_size_map();
107+
130108
(
131109
Some(aligned_malloc),
132110
Some(aligned_calloc),

crates/hyperion/src/egress/sync_entity_state.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,19 @@ impl Module for EntityStateSyncModule {
7070
let prev_xp: &mut [u16] =
7171
core::slice::from_raw_parts_mut(prev_xp.as_mut_ptr().cast(), count);
7272

73+
debug_assert_eq!(
74+
prev_xp.as_ptr() as usize & 63,
75+
0,
76+
"prev_xp is not 64-byte aligned"
77+
);
78+
7379
let mut xp = table.field_unchecked::<Xp>(3);
7480
let xp = xp.get_mut(..).unwrap();
7581
let xp: &mut [u16] =
7682
core::slice::from_raw_parts_mut(xp.as_mut_ptr().cast(), count);
7783

84+
debug_assert_eq!(xp.as_ptr() as usize & 63, 0, "xp is not 64-byte aligned");
85+
7886
simd_utils::copy_and_get_diff::<_, LANES>(
7987
prev_xp,
8088
xp,

0 commit comments

Comments
 (0)