diff options
author | Alex Orlenko <zxteam@protonmail.com> | 2024-06-18 15:15:07 +0100 |
---|---|---|
committer | Alex Orlenko <zxteam@protonmail.com> | 2024-06-18 16:09:06 +0100 |
commit | c23fa5aa6ccb84146a96a0cdfbdf9117c35d9657 (patch) | |
tree | 28bc619ef10ecb2ec94bd8d354e627cb7936ba8d | |
parent | 4f1d2abbcbe66eef207f2b1206cfd6d6c402fb8f (diff) | |
download | mlua-c23fa5aa6ccb84146a96a0cdfbdf9117c35d9657.zip |
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
-rw-r--r-- | benches/benchmark.rs | 18 | ||||
-rw-r--r-- | src/conversion.rs | 9 | ||||
-rw-r--r-- | src/lua.rs | 75 | ||||
-rw-r--r-- | src/types.rs | 54 | ||||
-rw-r--r-- | tests/tests.rs | 9 |
5 files changed, 89 insertions, 76 deletions
diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 245bd35..f8352e0 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -268,6 +268,23 @@ fn registry_value_create(c: &mut Criterion) { }); } +fn registry_value_get(c: &mut Criterion) { + let lua = Lua::new(); + lua.gc_stop(); + + let value = lua.create_registry_value("hello").unwrap(); + + c.bench_function("registry value [get]", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + assert_eq!(lua.registry_value::<LuaString>(&value).unwrap(), "hello"); + }, + BatchSize::SmallInput, + ); + }); +} + fn userdata_create(c: &mut Criterion) { struct UserData(#[allow(unused)] i64); impl LuaUserData for UserData {} @@ -406,6 +423,7 @@ criterion_group! { function_async_call_sum, registry_value_create, + registry_value_get, userdata_create, userdata_call_index, diff --git a/src/conversion.rs b/src/conversion.rs index f13b35a..6013ebf 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -453,10 +453,11 @@ impl<'lua> IntoLua<'lua> for &RegistryKey { return Err(Error::MismatchedRegistryKey); } - if self.is_nil() { - ffi::lua_pushnil(lua.state()); - } else { - ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, self.registry_id as _); + match self.id() { + ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()), + id => { + ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _); + } } Ok(()) } @@ -2049,7 +2049,7 @@ impl Lua { T: FromLua<'lua>, { let state = self.state(); - let value = unsafe { + unsafe { let _sg = StackGuard::new(state); check_stack(state, 3)?; @@ -2057,9 +2057,8 @@ impl Lua { push_string(state, name.as_bytes(), protect)?; ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - self.pop_value() - }; - T::from_lua(value, self) + T::from_stack(-1, self) + } } /// Removes a named value in the Lua registry. @@ -2082,22 +2081,21 @@ impl Lua { /// /// [`RegistryKey`]: crate::RegistryKey pub fn create_registry_value<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> { - let t = t.into_lua(self)?; - if t == Value::Nil { - // Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead - let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() }; - return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); - } - let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?; - self.push_value(t)?; + self.push(t)?; - // Try to reuse previously allocated slot let unref_list = (*self.extra.get()).registry_unref_list.clone(); + + // Check if the value is nil (no need to store it in the registry) + if ffi::lua_isnil(state, -1) != 0 { + return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list)); + } + + // Try to reuse previously allocated slot let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned") .as_mut() .and_then(|x| x.pop()); @@ -2107,7 +2105,7 @@ impl Lua { return Ok(RegistryKey::new(registry_id, unref_list)); } - // Allocate a new RegistryKey + // Allocate a new RegistryKey slot let registry_id = if self.unlikely_memory_error() { ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) } else { @@ -2131,18 +2129,16 @@ impl Lua { } let state = self.state(); - let value = match key.is_nil() { - true => Value::Nil, - false => unsafe { + match key.id() { + ffi::LUA_REFNIL => T::from_lua(Value::Nil, self), + registry_id => unsafe { let _sg = StackGuard::new(state); check_stack(state, 1)?; - let id = key.registry_id as Integer; - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, id); - self.pop_value() + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + T::from_stack(-1, self) }, - }; - T::from_lua(value, self) + } } /// Removes a value from the Lua registry. @@ -2180,29 +2176,32 @@ impl Lua { } let t = t.into_lua(self)?; - if t == Value::Nil && key.is_nil() { - // Nothing to replace - return Ok(()); - } else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL { - // We cannot update `LUA_REFNIL` slot - return Err(Error::runtime("cannot replace nil value with non-nil")); - } let state = self.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 2)?; - let id = key.registry_id as Integer; - if t == Value::Nil { - self.push_value(Value::Integer(id))?; - key.set_nil(true); - } else { - self.push_value(t)?; - key.set_nil(false); + match (t, key.id()) { + (Value::Nil, ffi::LUA_REFNIL) => { + // Do nothing, no need to replace nil with nil + } + (Value::Nil, registry_id) => { + // Remove the value + ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + key.set_id(ffi::LUA_REFNIL); + } + (value, ffi::LUA_REFNIL) => { + // Allocate a new `RegistryKey` + let new_key = self.create_registry_value(value)?; + key.set_id(new_key.take()); + } + (value, registry_id) => { + // It must be safe to replace the value without triggering memory error + self.push_value(value)?; + ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); + } } - // It must be safe to replace the value without triggering memory error - ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, id); } Ok(()) } diff --git a/src/types.rs b/src/types.rs index f21af2d..a87fdf6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::os::raw::{c_int, c_void}; use std::result::Result as StdResult; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Mutex}; use std::{fmt, mem, ptr}; @@ -204,26 +204,25 @@ pub(crate) struct DestructedUserdata; /// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value /// [`AnyUserData::user_value`]: crate::AnyUserData::user_value pub struct RegistryKey { - pub(crate) registry_id: c_int, - pub(crate) is_nil: AtomicBool, + pub(crate) registry_id: AtomicI32, pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>, } impl fmt::Debug for RegistryKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "RegistryKey({})", self.registry_id) + write!(f, "RegistryKey({})", self.id()) } } impl Hash for RegistryKey { fn hash<H: Hasher>(&self, state: &mut H) { - self.registry_id.hash(state) + self.id().hash(state) } } impl PartialEq for RegistryKey { fn eq(&self, other: &RegistryKey) -> bool { - self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list) + self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list) } } @@ -231,49 +230,46 @@ impl Eq for RegistryKey {} impl Drop for RegistryKey { fn drop(&mut self) { + let registry_id = self.id(); // We don't need to collect nil slot - if self.registry_id > ffi::LUA_REFNIL { + if registry_id > ffi::LUA_REFNIL { let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned"); if let Some(list) = unref_list.as_mut() { - list.push(self.registry_id); + list.push(registry_id); } } } } impl RegistryKey { - // Creates a new instance of `RegistryKey` + /// Creates a new instance of `RegistryKey` pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self { RegistryKey { - registry_id: id, - is_nil: AtomicBool::new(id == ffi::LUA_REFNIL), + registry_id: AtomicI32::new(id), unref_list, } } - // Destroys the `RegistryKey` without adding to the unref list - pub(crate) fn take(self) -> c_int { - let registry_id = self.registry_id; - unsafe { - ptr::read(&self.unref_list); - mem::forget(self); - } - registry_id + /// Returns the underlying Lua reference of this `RegistryKey` + #[inline(always)] + pub fn id(&self) -> c_int { + self.registry_id.load(Ordering::Relaxed) } - // Returns true if this `RegistryKey` holds a nil value + /// Sets the unique Lua reference key of this `RegistryKey` #[inline(always)] - pub(crate) fn is_nil(&self) -> bool { - self.is_nil.load(Ordering::Relaxed) + pub(crate) fn set_id(&self, id: c_int) { + self.registry_id.store(id, Ordering::Relaxed); } - // Marks value of this `RegistryKey` as `Nil` - #[inline(always)] - pub(crate) fn set_nil(&self, enabled: bool) { - // We cannot replace previous value with nil in as this will break - // Lua mechanism to find free keys. - // Instead, we set a special flag to mark value as nil. - self.is_nil.store(enabled, Ordering::Relaxed); + /// Destroys the `RegistryKey` without adding to the unref list + pub(crate) fn take(self) -> i32 { + let registry_id = self.id(); + unsafe { + ptr::read(&self.unref_list); + mem::forget(self); + } + registry_id } } diff --git a/tests/tests.rs b/tests/tests.rs index 9dd6a00..aab4f48 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -775,12 +775,11 @@ fn test_replace_registry_value() -> Result<()> { lua.replace_registry_value(&key, 123)?; assert_eq!(lua.registry_value::<i32>(&key)?, 123); - // It should be impossible to replace (initial) nil value with non-nil let key2 = lua.create_registry_value(Value::Nil)?; - match lua.replace_registry_value(&key2, "abc") { - Err(Error::RuntimeError(_)) => {} - r => panic!("expected RuntimeError, got {r:?}"), - } + lua.replace_registry_value(&key2, Value::Nil)?; + assert_eq!(lua.registry_value::<Value>(&key2)?, Value::Nil); + lua.replace_registry_value(&key2, "abc")?; + assert_eq!(lua.registry_value::<String>(&key2)?, "abc"); Ok(()) } |