Skip to content

Commit aebdd2a

Browse files
committed
Optimize RegistryKey internals
- Store single `AtomicI32` field instead of pair i32,AtomicBool - Make creation faster by skipping intermediate `Value` layer Add new `RegistryKey::id()` method to return underlying identifier
1 parent 4f1d2ab commit aebdd2a

File tree

5 files changed

+89
-76
lines changed

5 files changed

+89
-76
lines changed

benches/benchmark.rs

+18
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,23 @@ fn registry_value_create(c: &mut Criterion) {
268268
});
269269
}
270270

271+
fn registry_value_get(c: &mut Criterion) {
272+
let lua = Lua::new();
273+
lua.gc_stop();
274+
275+
let value = lua.create_registry_value("hello").unwrap();
276+
277+
c.bench_function("registry value [get]", |b| {
278+
b.iter_batched(
279+
|| collect_gc_twice(&lua),
280+
|_| {
281+
assert_eq!(lua.registry_value::<LuaString>(&value).unwrap(), "hello");
282+
},
283+
BatchSize::SmallInput,
284+
);
285+
});
286+
}
287+
271288
fn userdata_create(c: &mut Criterion) {
272289
struct UserData(#[allow(unused)] i64);
273290
impl LuaUserData for UserData {}
@@ -406,6 +423,7 @@ criterion_group! {
406423
function_async_call_sum,
407424

408425
registry_value_create,
426+
registry_value_get,
409427

410428
userdata_create,
411429
userdata_call_index,

src/conversion.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,11 @@ impl<'lua> IntoLua<'lua> for &RegistryKey {
453453
return Err(Error::MismatchedRegistryKey);
454454
}
455455

456-
if self.is_nil() {
457-
ffi::lua_pushnil(lua.state());
458-
} else {
459-
ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, self.registry_id as _);
456+
match self.id() {
457+
ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()),
458+
id => {
459+
ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _);
460+
}
460461
}
461462
Ok(())
462463
}

src/lua.rs

+37-38
Original file line numberDiff line numberDiff line change
@@ -2049,17 +2049,16 @@ impl Lua {
20492049
T: FromLua<'lua>,
20502050
{
20512051
let state = self.state();
2052-
let value = unsafe {
2052+
unsafe {
20532053
let _sg = StackGuard::new(state);
20542054
check_stack(state, 3)?;
20552055

20562056
let protect = !self.unlikely_memory_error();
20572057
push_string(state, name.as_bytes(), protect)?;
20582058
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
20592059

2060-
self.pop_value()
2061-
};
2062-
T::from_lua(value, self)
2060+
T::from_stack(-1, self)
2061+
}
20632062
}
20642063

20652064
/// Removes a named value in the Lua registry.
@@ -2082,22 +2081,21 @@ impl Lua {
20822081
///
20832082
/// [`RegistryKey`]: crate::RegistryKey
20842083
pub fn create_registry_value<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> {
2085-
let t = t.into_lua(self)?;
2086-
if t == Value::Nil {
2087-
// Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead
2088-
let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() };
2089-
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
2090-
}
2091-
20922084
let state = self.state();
20932085
unsafe {
20942086
let _sg = StackGuard::new(state);
20952087
check_stack(state, 4)?;
20962088

2097-
self.push_value(t)?;
2089+
self.push(t)?;
20982090

2099-
// Try to reuse previously allocated slot
21002091
let unref_list = (*self.extra.get()).registry_unref_list.clone();
2092+
2093+
// Check if the value is nil (no need to store it in the registry)
2094+
if ffi::lua_isnil(state, -1) != 0 {
2095+
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
2096+
}
2097+
2098+
// Try to reuse previously allocated slot
21012099
let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned")
21022100
.as_mut()
21032101
.and_then(|x| x.pop());
@@ -2107,7 +2105,7 @@ impl Lua {
21072105
return Ok(RegistryKey::new(registry_id, unref_list));
21082106
}
21092107

2110-
// Allocate a new RegistryKey
2108+
// Allocate a new RegistryKey slot
21112109
let registry_id = if self.unlikely_memory_error() {
21122110
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
21132111
} else {
@@ -2131,18 +2129,16 @@ impl Lua {
21312129
}
21322130

21332131
let state = self.state();
2134-
let value = match key.is_nil() {
2135-
true => Value::Nil,
2136-
false => unsafe {
2132+
match key.id() {
2133+
ffi::LUA_REFNIL => T::from_lua(Value::Nil, self),
2134+
registry_id => unsafe {
21372135
let _sg = StackGuard::new(state);
21382136
check_stack(state, 1)?;
21392137

2140-
let id = key.registry_id as Integer;
2141-
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, id);
2142-
self.pop_value()
2138+
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer);
2139+
T::from_stack(-1, self)
21432140
},
2144-
};
2145-
T::from_lua(value, self)
2141+
}
21462142
}
21472143

21482144
/// Removes a value from the Lua registry.
@@ -2180,29 +2176,32 @@ impl Lua {
21802176
}
21812177

21822178
let t = t.into_lua(self)?;
2183-
if t == Value::Nil && key.is_nil() {
2184-
// Nothing to replace
2185-
return Ok(());
2186-
} else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL {
2187-
// We cannot update `LUA_REFNIL` slot
2188-
return Err(Error::runtime("cannot replace nil value with non-nil"));
2189-
}
21902179

21912180
let state = self.state();
21922181
unsafe {
21932182
let _sg = StackGuard::new(state);
21942183
check_stack(state, 2)?;
21952184

2196-
let id = key.registry_id as Integer;
2197-
if t == Value::Nil {
2198-
self.push_value(Value::Integer(id))?;
2199-
key.set_nil(true);
2200-
} else {
2201-
self.push_value(t)?;
2202-
key.set_nil(false);
2185+
match (t, key.id()) {
2186+
(Value::Nil, ffi::LUA_REFNIL) => {
2187+
// Do nothing, no need to replace nil with nil
2188+
}
2189+
(Value::Nil, registry_id) => {
2190+
// Remove the value
2191+
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
2192+
key.set_id(ffi::LUA_REFNIL);
2193+
}
2194+
(value, ffi::LUA_REFNIL) => {
2195+
// Allocate a new `RegistryKey`
2196+
let new_key = self.create_registry_value(value)?;
2197+
key.set_id(new_key.take());
2198+
}
2199+
(value, registry_id) => {
2200+
// It must be safe to replace the value without triggering memory error
2201+
self.push_value(value)?;
2202+
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer);
2203+
}
22032204
}
2204-
// It must be safe to replace the value without triggering memory error
2205-
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, id);
22062205
}
22072206
Ok(())
22082207
}

src/types.rs

+25-29
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher};
44
use std::ops::{Deref, DerefMut};
55
use std::os::raw::{c_int, c_void};
66
use std::result::Result as StdResult;
7-
use std::sync::atomic::{AtomicBool, Ordering};
7+
use std::sync::atomic::{AtomicI32, Ordering};
88
use std::sync::{Arc, Mutex};
99
use std::{fmt, mem, ptr};
1010

@@ -204,76 +204,72 @@ pub(crate) struct DestructedUserdata;
204204
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
205205
/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value
206206
pub struct RegistryKey {
207-
pub(crate) registry_id: c_int,
208-
pub(crate) is_nil: AtomicBool,
207+
pub(crate) registry_id: AtomicI32,
209208
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
210209
}
211210

212211
impl fmt::Debug for RegistryKey {
213212
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214-
write!(f, "RegistryKey({})", self.registry_id)
213+
write!(f, "RegistryKey({})", self.id())
215214
}
216215
}
217216

218217
impl Hash for RegistryKey {
219218
fn hash<H: Hasher>(&self, state: &mut H) {
220-
self.registry_id.hash(state)
219+
self.id().hash(state)
221220
}
222221
}
223222

224223
impl PartialEq for RegistryKey {
225224
fn eq(&self, other: &RegistryKey) -> bool {
226-
self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list)
225+
self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list)
227226
}
228227
}
229228

230229
impl Eq for RegistryKey {}
231230

232231
impl Drop for RegistryKey {
233232
fn drop(&mut self) {
233+
let registry_id = self.id();
234234
// We don't need to collect nil slot
235-
if self.registry_id > ffi::LUA_REFNIL {
235+
if registry_id > ffi::LUA_REFNIL {
236236
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
237237
if let Some(list) = unref_list.as_mut() {
238-
list.push(self.registry_id);
238+
list.push(registry_id);
239239
}
240240
}
241241
}
242242
}
243243

244244
impl RegistryKey {
245-
// Creates a new instance of `RegistryKey`
245+
/// Creates a new instance of `RegistryKey`
246246
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
247247
RegistryKey {
248-
registry_id: id,
249-
is_nil: AtomicBool::new(id == ffi::LUA_REFNIL),
248+
registry_id: AtomicI32::new(id),
250249
unref_list,
251250
}
252251
}
253252

254-
// Destroys the `RegistryKey` without adding to the unref list
255-
pub(crate) fn take(self) -> c_int {
256-
let registry_id = self.registry_id;
257-
unsafe {
258-
ptr::read(&self.unref_list);
259-
mem::forget(self);
260-
}
261-
registry_id
253+
/// Returns the unique Lua reference key of this `RegistryKey`
254+
#[inline(always)]
255+
pub fn id(&self) -> c_int {
256+
self.registry_id.load(Ordering::Relaxed)
262257
}
263258

264-
// Returns true if this `RegistryKey` holds a nil value
259+
/// Sets the unique Lua reference key of this `RegistryKey`
265260
#[inline(always)]
266-
pub(crate) fn is_nil(&self) -> bool {
267-
self.is_nil.load(Ordering::Relaxed)
261+
pub(crate) fn set_id(&self, id: c_int) {
262+
self.registry_id.store(id, Ordering::Relaxed);
268263
}
269264

270-
// Marks value of this `RegistryKey` as `Nil`
271-
#[inline(always)]
272-
pub(crate) fn set_nil(&self, enabled: bool) {
273-
// We cannot replace previous value with nil in as this will break
274-
// Lua mechanism to find free keys.
275-
// Instead, we set a special flag to mark value as nil.
276-
self.is_nil.store(enabled, Ordering::Relaxed);
265+
/// Destroys the `RegistryKey` without adding to the unref list
266+
pub(crate) fn take(self) -> i32 {
267+
let registry_id = self.id();
268+
unsafe {
269+
ptr::read(&self.unref_list);
270+
mem::forget(self);
271+
}
272+
registry_id
277273
}
278274
}
279275

tests/tests.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -775,12 +775,11 @@ fn test_replace_registry_value() -> Result<()> {
775775
lua.replace_registry_value(&key, 123)?;
776776
assert_eq!(lua.registry_value::<i32>(&key)?, 123);
777777

778-
// It should be impossible to replace (initial) nil value with non-nil
779778
let key2 = lua.create_registry_value(Value::Nil)?;
780-
match lua.replace_registry_value(&key2, "abc") {
781-
Err(Error::RuntimeError(_)) => {}
782-
r => panic!("expected RuntimeError, got {r:?}"),
783-
}
779+
lua.replace_registry_value(&key2, Value::Nil)?;
780+
assert_eq!(lua.registry_value::<Value>(&key2)?, Value::Nil);
781+
lua.replace_registry_value(&key2, "abc")?;
782+
assert_eq!(lua.registry_value::<String>(&key2)?, "abc");
784783

785784
Ok(())
786785
}

0 commit comments

Comments
 (0)