Skip to content

Commit df0a44d

Browse files
committed
Make Lua reference values cheap to clone
Instead of locking the VM and making a copy on auxiliary thread, track number of references using Rust ref counter. This should also help reducing number of used references (they are limited to to 1M usually) on auxiliary thread.
1 parent f0806a6 commit df0a44d

File tree

7 files changed

+60
-44
lines changed

7 files changed

+60
-44
lines changed

benches/benchmark.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
128128
});
129129
}
130130

131+
fn table_ref_clone(c: &mut Criterion) {
132+
let lua = Lua::new();
133+
134+
let t = lua.create_table().unwrap();
135+
136+
c.bench_function("table [ref clone]", |b| {
137+
b.iter_batched(
138+
|| collect_gc_twice(&lua),
139+
|_| {
140+
let _t2 = t.clone();
141+
},
142+
BatchSize::SmallInput,
143+
);
144+
});
145+
}
146+
131147
fn function_create(c: &mut Criterion) {
132148
let lua = Lua::new();
133149

@@ -399,6 +415,7 @@ criterion_group! {
399415
table_traversal_pairs,
400416
table_traversal_for_each,
401417
table_traversal_sequence,
418+
table_ref_clone,
402419

403420
function_create,
404421
function_call_sum,

src/state/extra.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_hash::FxHashMap;
1212
use crate::error::Result;
1313
use crate::state::RawLua;
1414
use crate::stdlib::StdLib;
15-
use crate::types::{AppData, ReentrantMutex, XRc};
15+
use crate::types::{AppData, ReentrantMutex, ValueRefIndex, XRc};
1616
use crate::userdata::RawUserDataRegistry;
1717
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
1818

@@ -64,7 +64,7 @@ pub(crate) struct ExtraData {
6464
pub(super) wrapped_failure_top: usize,
6565
// Pool of `Thread`s (coroutines) for async execution
6666
#[cfg(feature = "async")]
67-
pub(super) thread_pool: Vec<c_int>,
67+
pub(super) thread_pool: Vec<ValueRefIndex>,
6868

6969
// Address of `WrappedFailure` metatable
7070
pub(super) wrapped_failure_mt_ptr: *const c_void,

src/state/raw.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ impl RawLua {
624624
#[cfg(feature = "async")]
625625
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
626626
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
627-
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
627+
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
628628
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
629629

630630
#[cfg(feature = "luau")]
@@ -645,8 +645,9 @@ impl RawLua {
645645
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
646646
let extra = &mut *self.extra.get();
647647
if extra.thread_pool.len() < extra.thread_pool.capacity() {
648-
extra.thread_pool.push(thread.0.index);
649-
thread.0.drop = false; // Prevent thread from being garbage collected
648+
if let Some(index) = thread.0.index_count.take() {
649+
extra.thread_pool.push(index);
650+
}
650651
}
651652
}
652653

@@ -827,13 +828,6 @@ impl RawLua {
827828
ValueRef::new(self, index)
828829
}
829830

830-
#[inline]
831-
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
832-
ffi::lua_pushvalue(self.ref_thread(), vref.index);
833-
let index = (*self.extra.get()).ref_stack_pop();
834-
ValueRef::new(self, index)
835-
}
836-
837831
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
838832
let ref_thread = self.ref_thread();
839833
mlua_debug_assert!(

src/table.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ impl ObjectLike for Table {
884884
R: FromLuaMulti,
885885
{
886886
// Convert table to a function and call via pcall that respects the `__call` metamethod.
887-
Function(self.0.copy()).call(args)
887+
Function(self.0.clone()).call(args)
888888
}
889889

890890
#[cfg(feature = "async")]
@@ -893,7 +893,7 @@ impl ObjectLike for Table {
893893
where
894894
R: FromLuaMulti,
895895
{
896-
Function(self.0.copy()).call_async(args)
896+
Function(self.0.clone()).call_async(args)
897897
}
898898

899899
#[inline]
@@ -941,7 +941,7 @@ impl ObjectLike for Table {
941941

942942
#[inline]
943943
fn to_string(&self) -> Result<StdString> {
944-
Value::Table(Table(self.0.copy())).to_string()
944+
Value::Table(Table(self.0.clone())).to_string()
945945
}
946946
}
947947

src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>;
1818
pub use app_data::{AppData, AppDataRef, AppDataRefMut};
1919
pub use either::Either;
2020
pub use registry_key::RegistryKey;
21-
pub(crate) use value_ref::ValueRef;
21+
pub(crate) use value_ref::{ValueRef, ValueRefIndex};
2222

2323
/// Type of Lua integer numbers.
2424
pub type Integer = ffi::lua_Integer;

src/types/value_ref.rs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
use std::fmt;
22
use std::os::raw::{c_int, c_void};
33

4+
use super::XRc;
45
use crate::state::{RawLua, WeakLua};
56

67
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
8+
#[derive(Clone)]
79
pub struct ValueRef {
810
pub(crate) lua: WeakLua,
11+
// Keep index separate to avoid additional indirection when accessing it.
912
pub(crate) index: c_int,
10-
pub(crate) drop: bool,
13+
// If `index_count` is `None`, the value does not need to be destroyed.
14+
pub(crate) index_count: Option<ValueRefIndex>,
15+
}
16+
17+
/// A reference to a Lua value index in the auxiliary thread.
18+
/// It's cheap to clone and can be used to track the number of references to a value.
19+
#[derive(Clone)]
20+
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
21+
22+
impl From<c_int> for ValueRefIndex {
23+
#[inline]
24+
fn from(index: c_int) -> Self {
25+
ValueRefIndex(XRc::new(index))
26+
}
1127
}
1228

1329
impl ValueRef {
1430
#[inline]
15-
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
31+
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
32+
let index = index.into();
1633
ValueRef {
1734
lua: lua.weak().clone(),
18-
index,
19-
drop: true,
35+
index: *index.0,
36+
index_count: Some(index),
2037
}
2138
}
2239

@@ -25,16 +42,6 @@ impl ValueRef {
2542
let lua = self.lua.lock();
2643
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
2744
}
28-
29-
/// Returns a copy of the value, which is valid as long as the original value is held.
30-
#[inline]
31-
pub(crate) fn copy(&self) -> Self {
32-
ValueRef {
33-
lua: self.lua.clone(),
34-
index: self.index,
35-
drop: false,
36-
}
37-
}
3845
}
3946

4047
impl fmt::Debug for ValueRef {
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
4350
}
4451
}
4552

46-
impl Clone for ValueRef {
47-
fn clone(&self) -> Self {
48-
unsafe { self.lua.lock().clone_ref(self) }
49-
}
50-
}
51-
5253
impl Drop for ValueRef {
5354
fn drop(&mut self) {
54-
if self.drop {
55-
if let Some(lua) = self.lua.try_lock() {
56-
unsafe { lua.drop_ref(self) };
55+
if let Some(ValueRefIndex(index)) = self.index_count.take() {
56+
// It's guaranteed that the inner value returns exactly once.
57+
// This means in particular that the value is not dropped.
58+
if XRc::into_inner(index).is_some() {
59+
if let Some(lua) = self.lua.try_lock() {
60+
unsafe { lua.drop_ref(self) };
61+
}
5762
}
5863
}
5964
}

src/userdata/object.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,22 @@ impl ObjectLike for AnyUserData {
1515
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
1616
// `lua_gettable` method used under the hood can work with any Lua value
1717
// that has `__index` metamethod
18-
Table(self.0.copy()).get_protected(key)
18+
Table(self.0.clone()).get_protected(key)
1919
}
2020

2121
#[inline]
2222
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
2323
// `lua_settable` method used under the hood can work with any Lua value
2424
// that has `__newindex` metamethod
25-
Table(self.0.copy()).set_protected(key, value)
25+
Table(self.0.clone()).set_protected(key, value)
2626
}
2727

2828
#[inline]
2929
fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
3030
where
3131
R: FromLuaMulti,
3232
{
33-
Function(self.0.copy()).call(args)
33+
Function(self.0.clone()).call(args)
3434
}
3535

3636
#[cfg(feature = "async")]
@@ -39,7 +39,7 @@ impl ObjectLike for AnyUserData {
3939
where
4040
R: FromLuaMulti,
4141
{
42-
Function(self.0.copy()).call_async(args)
42+
Function(self.0.clone()).call_async(args)
4343
}
4444

4545
#[inline]
@@ -88,6 +88,6 @@ impl ObjectLike for AnyUserData {
8888

8989
#[inline]
9090
fn to_string(&self) -> Result<StdString> {
91-
Value::UserData(AnyUserData(self.0.copy())).to_string()
91+
Value::UserData(AnyUserData(self.0.clone())).to_string()
9292
}
9393
}

0 commit comments

Comments
 (0)