#![allow(clippy::expect_used)]
use core::ffi::c_void;
use core::hint::black_box;
use std::path::PathBuf;
use std::sync::Arc;
use criterion::{Criterion, criterion_group, criterion_main};
use mlua::Function;
use mlua::Lua;
use polyplug::runtime::Runtime;
use polyplug::runtime::RuntimeBuilder;
use polyplug_abi::StringView;
use polyplug_abi::runtime::Compatibility;
use polyplug_abi::runtime::RuntimeConfig;
use polyplug_lua::LuaConfig;
use polyplug_lua::LuaLoader;
use polyplug_lua::ffi::PolyplugLuaLogBridge;
use polyplug_lua::ffi::polyplug_lua_log_trampoline;
fn bench_lua_dispatch(c: &mut Criterion) {
let lua: Lua = unsafe { Lua::unsafe_new() };
let lua_code: &str = r#"
function noop_dispatch(args, out)
return 0
end
"#;
lua.load(lua_code).exec().expect("Failed to load Lua code");
let noop_fn: Function = lua
.globals()
.get::<Function>("noop_dispatch")
.expect("Failed to get noop_dispatch function");
let mut group = c.benchmark_group("lua_dispatch");
group.bench_function("vm_dispatch_single_call", |b| {
b.iter(|| {
let args_i64: i64 = 0;
let out_i64: i64 = 0;
let _: Result<(), mlua::Error> = noop_fn.call::<()>((args_i64, out_i64));
black_box(())
})
});
group.bench_function("vm_dispatch_10_calls", |b| {
b.iter(|| {
let args_i64: i64 = 0;
let out_i64: i64 = 0;
for _ in 0..10 {
let _: Result<(), mlua::Error> = noop_fn.call::<()>((args_i64, out_i64));
}
black_box(())
})
});
group.finish();
}
fn bench_lua_vm_creation(c: &mut Criterion) {
let mut group = c.benchmark_group("lua_vm_creation");
group.bench_function("create_unsafe_vm", |b| {
b.iter(|| {
let lua: Lua = unsafe { Lua::unsafe_new() };
black_box(lua)
})
});
group.bench_function("create_vm_and_load_code", |b| {
b.iter(|| {
let lua: Lua = unsafe { Lua::unsafe_new() };
let lua_code: &str = r#"
function noop_dispatch(args, out)
return 0
end
"#;
lua.load(lua_code).exec().expect("Failed to load Lua code");
black_box(lua)
})
});
group.finish();
}
fn bench_native_baseline(c: &mut Criterion) {
let mut group = c.benchmark_group("native_baseline");
fn native_add(a: i32, b: i32) -> i32 {
a + b
}
group.bench_function("native_function_call", |b| {
b.iter(|| black_box(native_add(black_box(1), black_box(2))))
});
type NativeFn = extern "C" fn(i32, i32) -> i32;
extern "C" fn native_add_extern(a: i32, b: i32) -> i32 {
a + b
}
let func_ptr: NativeFn = native_add_extern;
group.bench_function("native_function_pointer_call", |b| {
b.iter(|| black_box(func_ptr(black_box(1), black_box(2))))
});
group.finish();
}
fn bench_lua_computation(c: &mut Criterion) {
let lua: Lua = unsafe { Lua::unsafe_new() };
let compute_code: &str = r#"
function compute_sum(args, out)
local sum = 0
for i = 1, 100 do
sum = sum + i
end
return sum
end
"#;
lua.load(compute_code)
.exec()
.expect("Failed to load compute code");
let compute_fn: Function = lua
.globals()
.get::<Function>("compute_sum")
.expect("Failed to get compute_sum function");
let mut group = c.benchmark_group("lua_computation");
group.bench_function("lua_computation_100_iterations", |b| {
b.iter(|| {
let args_i64: i64 = 0;
let out_i64: i64 = 0;
let _: Result<i64, mlua::Error> = compute_fn.call::<i64>((args_i64, out_i64));
black_box(())
})
});
group.finish();
}
fn bench_cached_dispatch(c: &mut Criterion) {
let lua: Lua = unsafe { Lua::unsafe_new() };
let lua_code: &str = r#"
function noop_dispatch(args, out)
return 0
end
"#;
lua.load(lua_code).exec().expect("Failed to load Lua code");
let cached_fn: Function = lua
.globals()
.get::<Function>("noop_dispatch")
.expect("Failed to get noop_dispatch function");
let mut group = c.benchmark_group("cached_dispatch");
group.bench_function("cached_function_single_call", |b| {
b.iter(|| {
let args_i64: i64 = 0;
let out_i64: i64 = 0;
let _: Result<(), mlua::Error> = cached_fn.call::<()>((args_i64, out_i64));
black_box(())
})
});
group.bench_function("cached_function_10_calls", |b| {
b.iter(|| {
let args_i64: i64 = 0;
let out_i64: i64 = 0;
for _ in 0..10 {
let _: Result<(), mlua::Error> = cached_fn.call::<()>((args_i64, out_i64));
}
black_box(())
})
});
group.finish();
}
unsafe extern "C" fn scalar_log_sink(
user_data: *mut c_void,
level: u32,
scope_ptr: *const u8,
scope_len: usize,
msg_ptr: *const u8,
msg_len: usize,
) {
black_box((user_data, level, scope_ptr, scope_len, msg_ptr, msg_len));
}
fn bench_lua_log_trampoline(c: &mut Criterion) {
let mut bridge: PolyplugLuaLogBridge = PolyplugLuaLogBridge {
callback: Some(scalar_log_sink),
user_data: core::ptr::null_mut(),
};
let bridge_ptr: *mut c_void = &mut bridge as *mut PolyplugLuaLogBridge as *mut c_void;
let scope: StringView = StringView::from_static(b"loader.lua");
let message: StringView = StringView::from_static(b"bundle dep 'x' has no bundle_id");
let mut group = c.benchmark_group("lua_log");
group.bench_function("trampoline_delivery", |b| {
b.iter(|| {
unsafe {
polyplug_lua_log_trampoline(
black_box(bridge_ptr),
black_box(2_u32),
black_box(scope),
black_box(message),
);
}
})
});
group.finish();
}
fn reload_plugin_script() -> &'static [u8] {
br#"
local ffi = require("ffi")
local function make_noop(_host) return {} end
local function impl_noop(_instance, _args_ptr, _out_ptr)
end
function polyplug_init(_registrar_ptr, _ctx_ptr)
return {
["test.loader"] = {
contract_version = 1,
plugin_name = "lua-reload-bench",
factory = make_noop,
functions = { [0] = impl_noop },
},
}, { code = 0 }
end
"#
}
fn write_temp_lua_bundle(name: &str) -> (tempfile::TempDir, PathBuf) {
let dir: tempfile::TempDir = tempfile::tempdir().expect("tempdir");
std::fs::write(dir.path().join("bundle.lua"), reload_plugin_script())
.expect("write bundle.lua");
let bundle_id: u64 = polyplug_utils::bundle_id(name);
let manifest: String = format!(
"id = {}\nname = \"{}\"\nloader = \"lua\"\nfile = \"bundle.lua\"\n",
bundle_id, name
);
std::fs::write(dir.path().join("manifest.toml"), manifest).expect("write manifest.toml");
let bundle_dir: PathBuf = dir.path().to_path_buf();
(dir, bundle_dir)
}
fn bench_lua_reload(c: &mut Criterion) {
let runtime: Arc<Runtime> = RuntimeBuilder::new()
.config(RuntimeConfig {
compatibility: Compatibility::Strict,
hot_reload_enabled: true,
..Default::default()
})
.loader(LuaLoader::new(LuaConfig::default()))
.build()
.expect("runtime build must succeed");
let (_dir, bundle_dir): (tempfile::TempDir, PathBuf) =
write_temp_lua_bundle("lua_reload_bench");
runtime
.load_bundle(&bundle_dir)
.expect("initial bundle load must succeed");
let mut group = c.benchmark_group("lua_reload");
group.bench_function("hot_reload_swap", |b| {
b.iter(|| {
runtime
.reload_bundle(black_box(bundle_dir.as_path()))
.expect("reload_bundle must succeed");
})
});
group.finish();
drop(_dir);
}
criterion_group!(
benches,
bench_lua_dispatch,
bench_lua_vm_creation,
bench_native_baseline,
bench_lua_computation,
bench_cached_dispatch,
bench_lua_log_trampoline,
bench_lua_reload
);
criterion_main!(benches);