Skip to content

Commit ef4d700

Browse files
committed
Optimize ValueRef (heap allocate only on clone)
Before this change, by default `ValueRef` allocate memory on heap, even for unique non-shared slots. After we allocate only when `ValueRef` is cloned and the slot becoming shared.
1 parent 07cf801 commit ef4d700

4 files changed

Lines changed: 182 additions & 35 deletions

File tree

src/state/extra.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub(crate) struct ExtraData {
6565
pub(super) wrapped_failure_top: usize,
6666
// Pool of `Thread`s (coroutines) for async execution
6767
#[cfg(feature = "async")]
68-
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
68+
pub(super) thread_pool: Vec<c_int>,
6969
// Map for implicit threads to root user-owned Thread
7070
#[cfg(feature = "async")]
7171
pub(super) thread_ownership_map: FxHashMap<*mut ffi::lua_State, *mut ffi::lua_State>,

src/state/raw.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ impl RawLua {
677677
#[cfg(feature = "async")]
678678
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
679679
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
680-
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
680+
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
681681
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
682682

683683
#[cfg(feature = "luau")]
@@ -716,7 +716,7 @@ impl RawLua {
716716
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
717717
let extra = &mut *self.extra.get();
718718
if extra.thread_pool.len() < extra.thread_pool.capacity()
719-
&& let Some(index) = thread.0.index_count.take()
719+
&& let Some(index) = thread.0.take_index()
720720
{
721721
extra.thread_pool.push(index);
722722
}

src/types.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ pub use either::Either;
2020
pub use registry_key::RegistryKey;
2121
pub(crate) use value_ref::ValueRef;
2222

23-
#[cfg(feature = "async")]
24-
pub(crate) use value_ref::ValueRefIndex;
25-
2623
/// Type of Lua integer numbers.
2724
pub type Integer = ffi::lua_Integer;
2825
/// Type of Lua floating point numbers.

src/types/value_ref.rs

Lines changed: 179 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,52 @@
1-
use std::fmt;
21
use std::os::raw::{c_int, c_void};
2+
use std::{fmt, ptr};
33

44
use super::XRc;
55
use crate::state::{RawLua, WeakLua};
66

7+
use self::ref_count::RefCount;
8+
79
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
8-
#[derive(Clone)]
910
pub struct ValueRef {
1011
pub(crate) lua: WeakLua,
11-
// Keep index separate to avoid additional indirection when accessing it.
1212
pub(crate) index: c_int,
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-
}
13+
count: RefCount,
2714
}
2815

2916
impl ValueRef {
3017
#[inline]
31-
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
32-
let index = index.into();
18+
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
3319
ValueRef {
3420
lua: lua.weak().clone(),
35-
index: *index.0,
36-
index_count: Some(index),
21+
index,
22+
count: RefCount::unique(),
3723
}
3824
}
3925

26+
#[cfg(feature = "async")]
27+
#[inline]
28+
pub(crate) fn take_index(&mut self) -> Option<c_int> {
29+
self.count.take(self.index)
30+
}
31+
4032
#[inline]
4133
pub(crate) fn to_pointer(&self) -> *const c_void {
4234
let lua = self.lua.lock();
4335
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
4436
}
4537
}
4638

39+
impl Clone for ValueRef {
40+
#[inline]
41+
fn clone(&self) -> Self {
42+
ValueRef {
43+
lua: self.lua.clone(),
44+
index: self.index,
45+
count: self.count.clone_shared(),
46+
}
47+
}
48+
}
49+
4750
impl fmt::Debug for ValueRef {
4851
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4952
write!(f, "Ref({:p})", self.to_pointer())
@@ -52,14 +55,10 @@ impl fmt::Debug for ValueRef {
5255

5356
impl Drop for ValueRef {
5457
fn drop(&mut 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-
&& let Some(lua) = self.lua.try_lock()
60-
{
61-
unsafe { lua.drop_ref(self) }
62-
}
58+
if self.count.drop_is_last()
59+
&& let Some(lua) = self.lua.try_lock()
60+
{
61+
unsafe { lua.drop_ref(self) }
6362
}
6463
}
6564
}
@@ -74,3 +73,154 @@ impl PartialEq for ValueRef {
7473
unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 }
7574
}
7675
}
76+
77+
// The counter is a pure refcount token.
78+
type Unit = ();
79+
const UNIQUE: *mut Unit = ptr::without_provenance_mut(1);
80+
const NONE: *mut Unit = ptr::null_mut();
81+
82+
impl RefCount {
83+
#[inline]
84+
fn unique() -> Self {
85+
Self::from_raw(UNIQUE)
86+
}
87+
88+
#[inline]
89+
fn clone_shared(&self) -> RefCount {
90+
let mut current = self.load();
91+
loop {
92+
if current != UNIQUE {
93+
if current != NONE {
94+
unsafe { XRc::increment_strong_count(current as *const Unit) };
95+
}
96+
return RefCount::from_raw(current);
97+
}
98+
// Lazily allocate the shared counter
99+
let shared = XRc::into_raw(XRc::new(())) as *mut Unit;
100+
match self.promote(shared) {
101+
Ok(()) => {
102+
unsafe { XRc::increment_strong_count(shared as *const Unit) };
103+
return RefCount::from_raw(shared);
104+
}
105+
Err(actual) => {
106+
unsafe { drop(XRc::from_raw(shared as *const Unit)) };
107+
current = actual;
108+
}
109+
}
110+
}
111+
}
112+
113+
/// Takes the slot if it's solely owned by `self`.
114+
///
115+
/// Returns `None` if the slot is still shared or already non-owning.
116+
#[cfg(feature = "async")]
117+
fn take(&mut self, index: c_int) -> Option<c_int> {
118+
match self.load() {
119+
current if current == UNIQUE => {
120+
self.swap_none();
121+
Some(index)
122+
}
123+
current if current == NONE => None,
124+
current => {
125+
// Shared: recyclable only if no other owner remains
126+
let rc = unsafe { XRc::from_raw(current as *const Unit) };
127+
if XRc::strong_count(&rc) == 1 {
128+
drop(rc);
129+
self.swap_none();
130+
Some(index)
131+
} else {
132+
let _ = XRc::into_raw(rc); // still shared
133+
None
134+
}
135+
}
136+
}
137+
}
138+
139+
/// Drops the reference and returns `true` if it was the last owner of the slot
140+
/// (so the slot must be freed).
141+
#[inline]
142+
fn drop_is_last(&mut self) -> bool {
143+
let current = self.load();
144+
if current == UNIQUE {
145+
true
146+
} else if current == NONE {
147+
false
148+
} else {
149+
unsafe { XRc::into_inner(XRc::from_raw(current as *const Unit)).is_some() }
150+
}
151+
}
152+
}
153+
154+
#[cfg(feature = "send")]
155+
mod ref_count {
156+
use std::sync::atomic::{AtomicPtr, Ordering};
157+
158+
use super::Unit;
159+
160+
pub(super) struct RefCount(AtomicPtr<Unit>);
161+
162+
impl RefCount {
163+
#[inline]
164+
pub(super) fn from_raw(ptr: *mut Unit) -> Self {
165+
RefCount(AtomicPtr::new(ptr))
166+
}
167+
168+
#[inline]
169+
pub(super) fn load(&self) -> *mut Unit {
170+
self.0.load(Ordering::Acquire)
171+
}
172+
173+
/// Replaces the `UNIQUE` tag with the freshly allocated shared counter (`new`).
174+
///
175+
/// Returns `Err(current)` if another thread promoted first.
176+
#[inline]
177+
pub(super) fn promote(&self, new: *mut Unit) -> Result<(), *mut Unit> {
178+
self.0
179+
.compare_exchange(super::UNIQUE, new, Ordering::AcqRel, Ordering::Acquire)
180+
.map(|_| ())
181+
}
182+
183+
#[cfg(feature = "async")]
184+
#[inline]
185+
pub(super) fn swap_none(&self) -> *mut Unit {
186+
self.0.swap(super::NONE, Ordering::AcqRel)
187+
}
188+
}
189+
}
190+
191+
#[cfg(not(feature = "send"))]
192+
mod ref_count {
193+
use std::cell::Cell;
194+
195+
use super::Unit;
196+
197+
pub(super) struct RefCount(Cell<*mut Unit>);
198+
199+
impl RefCount {
200+
#[inline]
201+
pub(super) fn from_raw(ptr: *mut Unit) -> Self {
202+
RefCount(Cell::new(ptr))
203+
}
204+
205+
#[inline]
206+
pub(super) fn load(&self) -> *mut Unit {
207+
self.0.get()
208+
}
209+
210+
/// Replaces the `UNIQUE` tag with the freshly allocated shared counter `new`.
211+
///
212+
/// Never fails.
213+
#[inline]
214+
pub(super) fn promote(&self, new: *mut Unit) -> Result<(), *mut Unit> {
215+
debug_assert_eq!(self.0.get(), super::UNIQUE);
216+
self.0.set(new);
217+
Ok(())
218+
}
219+
220+
#[cfg(feature = "async")]
221+
#[inline]
222+
pub(super) fn swap_none(&self) -> *mut Unit {
223+
self.0.replace(super::NONE)
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)