Skip to content

Commit ceac3a0

Browse files
committed
optimize code
1 parent 4ec0c9c commit ceac3a0

File tree

2 files changed

+129
-65
lines changed

2 files changed

+129
-65
lines changed

crates/luars/src/gc.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ pub struct GC {
8888
gc_kind: GCKind, // KGC_INC or KGC_GEN
8989
last_atomic: usize, // For generational mode
9090

91+
// Shrink optimization - avoid frequent shrinking
92+
shrink_cooldown: u32, // Shrink 冷却计数器
93+
shrink_threshold: u32, // Shrink 阈值
94+
9195
// Generational GC state
9296
allocations_since_minor_gc: usize,
9397
minor_gc_count: usize,
@@ -130,6 +134,8 @@ impl GC {
130134
gen_major_mul: 100, // LUAI_GENMAJORMUL
131135
gc_kind: GCKind::Generational, // Default to generational (like Lua 5.4)
132136
last_atomic: 0,
137+
shrink_cooldown: 0,
138+
shrink_threshold: 10, // 每 10 次 GC 才 shrink 一次
133139
allocations_since_minor_gc: 0,
134140
minor_gc_count: 0,
135141
collection_count: 0,
@@ -300,11 +306,14 @@ impl GC {
300306
self.allocations_since_minor_gc = 0;
301307
self.minor_gc_count += 1;
302308

303-
// Shrink ObjectPool HashMaps after collection
304-
// This is critical for performance: after deleting many entries,
305-
// HashMap capacity remains large, causing O(log n) lookups instead of O(1)
306-
if collected > 100 {
309+
// Shrink only if:
310+
// 1. Collected many objects (>1000)
311+
// 2. Cooldown expired
312+
if collected > 1000 && self.shrink_cooldown == 0 {
307313
object_pool.shrink_to_fit();
314+
self.shrink_cooldown = self.shrink_threshold;
315+
} else if self.shrink_cooldown > 0 {
316+
self.shrink_cooldown -= 1;
308317
}
309318

310319
collected
@@ -366,8 +375,9 @@ impl GC {
366375
self.allocations_since_minor_gc = 0;
367376
self.adjust_threshold();
368377

369-
// Always shrink after major GC
378+
// Major GC 后总是 shrink,但重置冷却期
370379
object_pool.shrink_to_fit();
380+
self.shrink_cooldown = self.shrink_threshold;
371381

372382
collected
373383
}

crates/luars/src/object_pool.rs

Lines changed: 114 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,91 @@ use std::cell::RefCell;
88
use std::collections::HashMap;
99
use std::rc::Rc;
1010

11+
/// Slot-based storage with free list for O(1) allocation and deallocation
12+
struct SlotVec<T> {
13+
slots: Vec<Option<T>>,
14+
free_list: Vec<u32>,
15+
count: usize,
16+
}
17+
18+
#[allow(unused)]
19+
impl<T> SlotVec<T> {
20+
fn new() -> Self {
21+
Self {
22+
slots: Vec::new(),
23+
free_list: Vec::new(),
24+
count: 0,
25+
}
26+
}
27+
28+
fn with_capacity(capacity: usize) -> Self {
29+
Self {
30+
slots: Vec::with_capacity(capacity),
31+
free_list: Vec::with_capacity(capacity / 4),
32+
count: 0,
33+
}
34+
}
35+
36+
/// O(1) insertion - reuse free slot or append new slot
37+
#[inline]
38+
fn insert(&mut self, value: T) -> u32 {
39+
self.count += 1;
40+
41+
if let Some(free_id) = self.free_list.pop() {
42+
self.slots[free_id as usize] = Some(value);
43+
free_id
44+
} else {
45+
let id = self.slots.len() as u32;
46+
self.slots.push(Some(value));
47+
id
48+
}
49+
}
50+
51+
/// O(1) lookup - direct array indexing
52+
#[inline]
53+
fn get(&self, id: u32) -> Option<&T> {
54+
self.slots.get(id as usize).and_then(|slot| slot.as_ref())
55+
}
56+
57+
/// O(1) removal - mark as free and add to free list
58+
#[inline]
59+
fn remove(&mut self, id: u32) -> Option<T> {
60+
if let Some(slot) = self.slots.get_mut(id as usize) {
61+
if let Some(value) = slot.take() {
62+
self.free_list.push(id);
63+
self.count -= 1;
64+
return Some(value);
65+
}
66+
}
67+
None
68+
}
69+
70+
#[inline]
71+
fn len(&self) -> usize {
72+
self.count
73+
}
74+
75+
/// Shrink memory after GC
76+
fn shrink_to_fit(&mut self) {
77+
if self.free_list.len() < self.slots.len() / 4 {
78+
self.free_list.shrink_to_fit();
79+
return;
80+
}
81+
82+
while let Some(None) = self.slots.last() {
83+
let removed_id = self.slots.len() - 1;
84+
self.slots.pop();
85+
86+
if let Some(pos) = self.free_list.iter().rposition(|&id| id as usize == removed_id) {
87+
self.free_list.swap_remove(pos);
88+
}
89+
}
90+
91+
self.slots.shrink_to_fit();
92+
self.free_list.shrink_to_fit();
93+
}
94+
}
95+
1196
/// Object IDs - u32 is enough for most use cases (4 billion objects)
1297
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1398
pub struct StringId(pub u32);
@@ -63,17 +148,10 @@ impl FunctionId {
63148

64149
/// Object Pool for all heap-allocated Lua objects
65150
pub struct ObjectPool {
66-
// Object storage - 所有类型都用 Rc 包装以防止 HashMap rehash 导致指针失效
67-
strings: HashMap<StringId, Rc<LuaString>>,
68-
tables: HashMap<TableId, Rc<RefCell<LuaTable>>>,
69-
userdata: HashMap<UserdataId, Rc<RefCell<LuaUserdata>>>,
70-
functions: HashMap<FunctionId, Rc<RefCell<lua_value::LuaFunction>>>,
71-
72-
// ID generators
73-
next_string_id: StringId,
74-
next_table_id: TableId,
75-
next_userdata_id: UserdataId,
76-
next_function_id: FunctionId,
151+
strings: SlotVec<Rc<LuaString>>,
152+
tables: SlotVec<Rc<RefCell<LuaTable>>>,
153+
userdata: SlotVec<Rc<RefCell<LuaUserdata>>>,
154+
functions: SlotVec<Rc<RefCell<lua_value::LuaFunction>>>,
77155

78156
// String interning table (hash -> id mapping)
79157
// For strings ≤ 64 bytes, we intern them for memory efficiency
@@ -84,14 +162,10 @@ pub struct ObjectPool {
84162
impl ObjectPool {
85163
pub fn new() -> Self {
86164
ObjectPool {
87-
strings: HashMap::with_capacity(128),
88-
tables: HashMap::with_capacity(16),
89-
userdata: HashMap::with_capacity(0),
90-
functions: HashMap::with_capacity(64),
91-
next_string_id: StringId(1), // 0 reserved for null/invalid
92-
next_table_id: TableId(1),
93-
next_userdata_id: UserdataId(1),
94-
next_function_id: FunctionId(1),
165+
strings: SlotVec::with_capacity(128),
166+
tables: SlotVec::with_capacity(16),
167+
userdata: SlotVec::with_capacity(0),
168+
functions: SlotVec::with_capacity(64),
95169
string_intern: HashMap::with_capacity(128),
96170
max_intern_length: 64,
97171
}
@@ -115,43 +189,37 @@ impl ObjectPool {
115189
// Check intern table
116190
if let Some(&id) = self.string_intern.get(&hash) {
117191
// Verify content (hash collision check)
118-
if let Some(existing) = self.strings.get(&id) {
192+
if let Some(existing) = self.strings.get(id.0) {
119193
if existing.as_str() == s {
120194
return id;
121195
}
122196
}
123197
}
124198

125199
// Create new interned string
126-
let id = self.next_string_id;
127-
self.next_string_id = id.next();
128-
129200
let lua_string = Rc::new(LuaString::new(s.to_string()));
130-
self.strings.insert(id, lua_string);
201+
let slot_id = self.strings.insert(lua_string);
202+
let id = StringId(slot_id);
131203
self.string_intern.insert(hash, id);
132204

133205
id
134206
} else {
135207
// Long string - no interning
136-
let id = self.next_string_id;
137-
self.next_string_id = id.next();
138-
139208
let lua_string = Rc::new(LuaString::new(s.to_string()));
140-
self.strings.insert(id, lua_string);
141-
142-
id
209+
let slot_id = self.strings.insert(lua_string);
210+
StringId(slot_id)
143211
}
144212
}
145213

146214
/// Get string by ID
147215
#[inline]
148216
pub fn get_string(&self, id: StringId) -> Option<&Rc<LuaString>> {
149-
self.strings.get(&id)
217+
self.strings.get(id.0)
150218
}
151219

152220
/// Remove string (called by GC)
153221
pub fn remove_string(&mut self, id: StringId) -> Option<Rc<LuaString>> {
154-
if let Some(string) = self.strings.remove(&id) {
222+
if let Some(string) = self.strings.remove(id.0) {
155223
// Also remove from intern table if present
156224
if string.as_str().len() <= self.max_intern_length {
157225
use std::collections::hash_map::DefaultHasher;
@@ -177,78 +245,64 @@ impl ObjectPool {
177245

178246
/// Create a new table
179247
pub fn create_table(&mut self, array_size: usize, hash_size: usize) -> TableId {
180-
let id = self.next_table_id;
181-
self.next_table_id = id.next();
182-
183-
self.tables.insert(
184-
id,
185-
Rc::new(RefCell::new(LuaTable::new(array_size, hash_size))),
186-
);
187-
188-
id
248+
let table = Rc::new(RefCell::new(LuaTable::new(array_size, hash_size)));
249+
let slot_id = self.tables.insert(table);
250+
TableId(slot_id)
189251
}
190252

191253
/// Get table by ID
192254
#[inline]
193255
pub fn get_table(&self, id: TableId) -> Option<&Rc<RefCell<LuaTable>>> {
194-
self.tables.get(&id)
256+
self.tables.get(id.0)
195257
}
196258

197259
/// Remove table (called by GC)
198260
pub fn remove_table(&mut self, id: TableId) -> Option<Rc<RefCell<LuaTable>>> {
199-
self.tables.remove(&id)
261+
self.tables.remove(id.0)
200262
}
201263

202264
// ============ Userdata Operations ============
203265

204266
/// Create new userdata
205267
pub fn create_userdata(&mut self, data: LuaUserdata) -> UserdataId {
206-
let id = self.next_userdata_id;
207-
self.next_userdata_id = id.next();
208-
209-
self.userdata.insert(id, Rc::new(RefCell::new(data)));
210-
211-
id
268+
let slot_id = self.userdata.insert(Rc::new(RefCell::new(data)));
269+
UserdataId(slot_id)
212270
}
213271

214272
/// Get userdata by ID
215273
#[inline]
216274
pub fn get_userdata(&self, id: UserdataId) -> Option<&Rc<RefCell<LuaUserdata>>> {
217-
self.userdata.get(&id)
275+
self.userdata.get(id.0)
218276
}
219277

220278
/// Get mutable userdata by ID (actually returns &Rc<RefCell<>> - mutate via borrow_mut)
221279
#[inline]
222280
pub fn get_userdata_mut(&mut self, id: UserdataId) -> Option<&Rc<RefCell<LuaUserdata>>> {
223-
self.userdata.get(&id)
281+
self.userdata.get(id.0)
224282
}
225283

226284
/// Remove userdata (called by GC)
227285
pub fn remove_userdata(&mut self, id: UserdataId) -> Option<Rc<RefCell<LuaUserdata>>> {
228-
self.userdata.remove(&id)
286+
self.userdata.remove(id.0)
229287
}
230288

231289
// ============ Function Operations ============
232290

233291
/// Create a new function
234292
pub fn create_function(&mut self, func: LuaFunction) -> FunctionId {
235-
let id = self.next_function_id;
236-
self.next_function_id = id.next();
237-
238-
self.functions
239-
.insert(id, std::rc::Rc::new(RefCell::new(func)));
240-
id
293+
let slot_id = self.functions.insert(Rc::new(RefCell::new(func)));
294+
FunctionId(slot_id)
241295
}
242296

243297
/// Get function by ID
244298
#[inline]
245299
pub fn get_function(&self, id: FunctionId) -> Option<&std::rc::Rc<RefCell<LuaFunction>>> {
246-
self.functions.get(&id)
300+
self.functions.get(id.0)
247301
}
248302

249303
/// Remove function (called by GC)
250304
pub fn remove_function(&mut self, id: FunctionId) -> Option<std::rc::Rc<RefCell<LuaFunction>>> {
251-
self.functions.remove(&id)
305+
self.functions.remove(id.0)
252306
}
253307

254308
// ============ Statistics ============

0 commit comments

Comments
 (0)