Skip to content

Commit feec72b

Browse files
committed
Reduce number of allocations when calling async function
Instead of creating a uniq poller with upvalue on each async call, return future directly and pass it to the poller This also gives about 3-5% perf improvements
1 parent 0611906 commit feec72b

File tree

2 files changed

+27
-27
lines changed

2 files changed

+27
-27
lines changed

src/state.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,9 +2176,13 @@ impl Lua {
21762176
None => unsafe {
21772177
let lua = self.lock();
21782178
let state = lua.state();
2179-
let _sg = StackGuard::with_top(state, 0);
2180-
let nvals = ffi::lua_gettop(state);
2181-
Poll::Ready(R::from_stack_multi(nvals, &lua))
2179+
let top = ffi::lua_gettop(state);
2180+
if top == 0 || ffi::lua_type(state, 1) != ffi::LUA_TUSERDATA {
2181+
// This must be impossible scenario if used correctly
2182+
return Poll::Ready(R::from_stack_multi(0, &lua));
2183+
}
2184+
let _sg = StackGuard::with_top(state, 1);
2185+
Poll::Ready(R::from_stack_multi(top - 1, &lua))
21822186
},
21832187
})
21842188
.await

src/state/raw.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ impl RawLua {
12871287
}
12881288
}
12891289

1290-
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int {
1290+
unsafe extern "C-unwind" fn get_future_callback(state: *mut ffi::lua_State) -> c_int {
12911291
// Async functions cannot be scoped and therefore destroyed,
12921292
// so the first upvalue is always valid
12931293
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
@@ -1301,33 +1301,27 @@ impl RawLua {
13011301
let extra = XRc::clone(&(*upvalue).extra);
13021302
let protect = !rawlua.unlikely_memory_error();
13031303
push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?;
1304-
if protect {
1305-
protect_lua!(state, 1, 1, fn(state) {
1306-
ffi::lua_pushcclosure(state, poll_future, 1);
1307-
})?;
1308-
} else {
1309-
ffi::lua_pushcclosure(state, poll_future, 1);
1310-
}
13111304

13121305
Ok(1)
13131306
})
13141307
}
13151308

13161309
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
1317-
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
1318-
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
1310+
// Future is always passed in the first argument
1311+
let future = get_userdata::<AsyncPollUpvalue>(state, 1);
1312+
callback_error_ext(state, (*future).extra.get(), true, |extra, nargs| {
13191313
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
13201314
// The lock must be already held as the future is polled
13211315
let rawlua = (*extra).raw_lua();
13221316

1323-
if nargs == 1 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
1317+
if nargs == 2 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
13241318
// Destroy the future and terminate the Lua thread
1325-
(*upvalue).data.take();
1319+
(*future).data.take();
13261320
ffi::lua_pushinteger(state, -1);
13271321
return Ok(1);
13281322
}
13291323

1330-
let fut = &mut (*upvalue).data;
1324+
let fut = &mut (*future).data;
13311325
let mut ctx = Context::from_waker(rawlua.waker());
13321326
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
13331327
Some(Poll::Pending) => {
@@ -1366,7 +1360,7 @@ impl RawLua {
13661360
}
13671361

13681362
let state = self.state();
1369-
let get_poll = unsafe {
1363+
let get_future = unsafe {
13701364
let _sg = StackGuard::new(state);
13711365
check_stack(state, 4)?;
13721366

@@ -1376,10 +1370,10 @@ impl RawLua {
13761370
push_internal_userdata(state, upvalue, protect)?;
13771371
if protect {
13781372
protect_lua!(state, 1, 1, fn(state) {
1379-
ffi::lua_pushcclosure(state, call_callback, 1);
1373+
ffi::lua_pushcclosure(state, get_future_callback, 1);
13801374
})?;
13811375
} else {
1382-
ffi::lua_pushcclosure(state, call_callback, 1);
1376+
ffi::lua_pushcclosure(state, get_future_callback, 1);
13831377
}
13841378

13851379
Function(self.pop_ref())
@@ -1398,15 +1392,17 @@ impl RawLua {
13981392
let coroutine = lua.globals().get::<Table>("coroutine")?;
13991393

14001394
// Prepare environment for the async poller
1401-
let env = lua.create_table_with_capacity(0, 3)?;
1402-
env.set("get_poll", get_poll)?;
1395+
let env = lua.create_table_with_capacity(0, 4)?;
1396+
env.set("get_future", get_future)?;
1397+
env.set("poll", unsafe { lua.create_c_function(poll_future)? })?;
14031398
env.set("yield", coroutine.get::<Function>("yield")?)?;
14041399
env.set("unpack", unsafe { lua.create_c_function(unpack)? })?;
14051400

14061401
lua.load(
14071402
r#"
1408-
local poll = get_poll(...)
1409-
local nres, res, res2 = poll()
1403+
local poll, yield = poll, yield
1404+
local future = get_future(...)
1405+
local nres, res, res2 = poll(future)
14101406
while true do
14111407
-- Poll::Ready branch, `nres` is the number of results
14121408
if nres ~= nil then
@@ -1430,13 +1426,13 @@ impl RawLua {
14301426
-- `res` is a "pending" value
14311427
-- `yield` can return a signal to drop the future that we should propagate
14321428
-- to the poller
1433-
nres, res, res2 = poll(yield(res))
1429+
nres, res, res2 = poll(future, yield(res))
14341430
elseif res2 == 0 then
1435-
nres, res, res2 = poll(yield())
1431+
nres, res, res2 = poll(future, yield())
14361432
elseif res2 == 1 then
1437-
nres, res, res2 = poll(yield(res))
1433+
nres, res, res2 = poll(future, yield(res))
14381434
else
1439-
nres, res, res2 = poll(yield(unpack(res, res2)))
1435+
nres, res, res2 = poll(future, yield(unpack(res, res2)))
14401436
end
14411437
end
14421438
"#,

0 commit comments

Comments
 (0)