Skip to content

Commit 9eaefe6

Browse files
committed
update gc
1 parent 5f723a0 commit 9eaefe6

File tree

4 files changed

+71
-25
lines changed

4 files changed

+71
-25
lines changed

crates/luars/src/gc.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ pub struct GC {
9292
allocations_since_minor_gc: usize,
9393
minor_gc_count: usize,
9494

95+
// Incremental collection throttling
96+
// To avoid overhead of collecting roots every check, only run GC every N checks
97+
check_counter: u32,
98+
check_interval: u32, // Run GC every N checks (default: 10)
99+
95100
// Finalization support (__gc metamethod)
96101
finalize_queue: Vec<(GcObjectType, u32)>, // Objects awaiting finalization
97102

@@ -133,6 +138,8 @@ impl GC {
133138
shrink_threshold: 10, // 每 10 次 GC 才 shrink 一次
134139
allocations_since_minor_gc: 0,
135140
minor_gc_count: 0,
141+
check_counter: 0,
142+
check_interval: 1000, // Run GC every 1000 checks when debt > 0 (reduce overhead)
136143
finalize_queue: Vec::new(), // __gc finalizer queue
137144
collection_count: 0,
138145
stats: GCStats::default(),
@@ -178,12 +185,34 @@ impl GC {
178185
self.gc_debt > 0
179186
}
180187

188+
/// Increment check counter for throttling GC collection
189+
#[inline]
190+
pub fn increment_check_counter(&mut self) {
191+
self.check_counter += 1;
192+
}
193+
194+
/// Check if we should actually run GC collection (throttled)
195+
/// This reduces overhead by not collecting roots on every check
196+
#[inline]
197+
pub fn should_run_collection(&self) -> bool {
198+
self.check_counter >= self.check_interval
199+
}
200+
201+
/// Reset check counter after running GC
202+
#[inline]
203+
fn reset_check_counter(&mut self) {
204+
self.check_counter = 0;
205+
}
206+
181207
/// Perform one step of GC work (like luaC_step in Lua 5.4)
182208
pub fn step(&mut self, roots: &[LuaValue], object_pool: &mut crate::object_pool::ObjectPool) {
183209
if !self.should_collect() {
184210
return;
185211
}
186212

213+
// Reset throttling counter
214+
self.reset_check_counter();
215+
187216
// Determine which collection to run
188217
match self.gc_kind {
189218
GCKind::Generational => {

crates/luars/src/lua_vm/dispatcher/table_instructions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub fn exec_newtable(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
4242
let table = vm.create_table(array_size, hash_size);
4343
vm.register_stack[base_ptr + a] = table;
4444

45+
// GC checkpoint: table now safely stored in register
46+
vm.check_gc();
47+
4548
Ok(())
4649
}
4750

crates/luars/src/lua_vm/dispatcher/upvalue_instructions.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ pub fn exec_closure(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
140140
let closure = vm.create_function(proto, upvalues);
141141
vm.register_stack[base_ptr + a] = closure;
142142

143+
// GC checkpoint: closure now safely stored in register
144+
vm.check_gc();
145+
143146
Ok(())
144147
}
145148

@@ -237,6 +240,9 @@ pub fn exec_concat(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
237240

238241
let result_value = vm.create_string(&result);
239242
vm.register_stack[base_ptr + a] = result_value;
243+
244+
// No GC check for fast path - rely on debt mechanism
245+
// Only large allocations trigger automatic GC
240246
return Ok(());
241247
}
242248

@@ -318,6 +324,7 @@ pub fn exec_concat(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
318324

319325
vm.register_stack[base_ptr + a] = result_value;
320326

327+
// No GC check - rely on debt mechanism
321328
Ok(())
322329
}
323330

crates/luars/src/lua_vm/mod.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,7 @@ impl LuaVM {
252252
/// Main execution loop - interprets bytecode instructions
253253
/// Main VM execution loop - MAXIMUM PERFORMANCE
254254
/// 零返回值,零分支,直接调度
255-
fn run(&mut self) -> LuaResult<LuaValue> {
256-
let mut instruction_count: u32 = 0;
257-
255+
fn run(&mut self) -> LuaResult<LuaValue> {
258256
loop {
259257
// 检查是否有帧
260258
if self.frames.is_empty() {
@@ -265,27 +263,17 @@ impl LuaVM {
265263
.unwrap_or(LuaValue::nil()));
266264
}
267265

268-
// 周期性 GC 检查 (每 10000 条指令)
269-
instruction_count += 1;
270-
if instruction_count % 10000 == 0 {
271-
self.check_gc();
272-
}
273-
274-
// === 极简主循环:直接调度,无返回值 ===
275266
let frame = unsafe { self.frames.last_mut().unwrap_unchecked() };
276267
let instr = unsafe { *frame.code_ptr.add(frame.pc) };
277268
frame.pc += 1;
278269

279-
// 直接调度 - 指令自己处理一切(包括 Skip1、Return 等)
270+
280271
if let Err(e) = dispatch_instruction(self, instr) {
281272
match e {
282273
LuaError::Yield(_) => return Ok(LuaValue::nil()),
283274
_ => return Err(e),
284275
}
285276
}
286-
287-
// 检查函数返回(通过 frames 是否改变来判断)
288-
// 这个检查很便宜,因为大部分时候都不会返回
289277
}
290278
}
291279

@@ -1184,6 +1172,15 @@ impl LuaVM {
11841172
/// - Long string: 1 Box allocation, GC registration, no pooling
11851173
pub fn create_string(&mut self, s: &str) -> LuaValue {
11861174
let id = self.object_pool.create_string(s);
1175+
1176+
// Estimate memory cost: string data + LuaString struct overhead
1177+
// LuaString: ~32 bytes base + string length
1178+
let estimated_bytes = 32 + s.len();
1179+
self.gc.record_allocation(estimated_bytes);
1180+
1181+
// GC check MUST NOT happen here - object not yet protected!
1182+
// Caller must call check_gc() AFTER storing value in register
1183+
11871184
// Get pointer from object pool for direct access
11881185
let ptr = self
11891186
.object_pool
@@ -1207,10 +1204,20 @@ impl LuaVM {
12071204
pub fn create_table(&mut self, array_size: usize, hash_size: usize) -> LuaValue {
12081205
let id = self.object_pool.create_table(array_size, hash_size);
12091206

1207+
// Estimate memory cost: base overhead + array + hash part
1208+
// Base: ~64 bytes for LuaTable struct + Rc overhead
1209+
// Array: array_size * 16 bytes per LuaValue
1210+
// Hash: hash_size * 32 bytes per Node (key+value pair)
1211+
let estimated_bytes = 64 + (array_size * 16) + (hash_size * 32);
1212+
self.gc.record_allocation(estimated_bytes);
1213+
12101214
// Register with GC for manual collection
12111215
self.gc
12121216
.register_object(id.0, crate::gc::GcObjectType::Table);
12131217

1218+
// GC check MUST NOT happen here - object not yet protected!
1219+
// Caller must call check_gc() AFTER storing value in register
1220+
12141221
// Get pointer from object pool for direct access
12151222
// OPTIMIZATION: Use unwrap_unchecked in release mode since we just created the table
12161223
let ptr = unsafe {
@@ -1355,23 +1362,23 @@ impl LuaVM {
13551362
}
13561363
}
13571364

1358-
/// Check if GC should run and collect garbage if needed
1359-
#[allow(unused)]
1360-
fn maybe_collect_garbage(&mut self) {
1361-
if self.gc.should_collect() {
1362-
self.collect_garbage();
1363-
}
1364-
}
1365-
13661365
/// Check GC and run a step if needed (like luaC_checkGC in Lua 5.4)
1367-
/// This should be called after allocating new objects
1368-
/// CURRENTLY DISABLED: Needs better object lifetime management
1369-
#[inline]
1366+
/// This is called after allocating new objects (strings, tables, functions)
1367+
/// Uses GC debt mechanism: runs when debt > 0
1368+
///
1369+
/// OPTIMIZATION: Use incremental collection with work budget
13701370
fn check_gc(&mut self) {
1371+
// Fast path: check debt without collecting roots
13711372
if !self.gc.should_collect() {
13721373
return;
13731374
}
13741375

1376+
// Incremental GC: only collect every N checks to reduce overhead
1377+
self.gc.increment_check_counter();
1378+
if !self.gc.should_run_collection() {
1379+
return;
1380+
}
1381+
13751382
// Collect roots: all reachable objects from VM state
13761383
let mut roots = Vec::new();
13771384

0 commit comments

Comments
 (0)