初始化内容

This commit is contained in:
2026-09-16 14:07:40 +08:00
parent 6934b390bf
commit 4f643c1e7c
18350 changed files with 6088489 additions and 305 deletions
+105
View File
@@ -0,0 +1,105 @@
cmake_minimum_required(VERSION 3.10)
cmake_policy (SET CMP0169 OLD)
include(FetchContent)
set(LUABRIDGE_BENCHMARK_WITH_SOL3 ON CACHE BOOL "Build Sol3 benchmark target")
set(LUABRIDGE_SOL2_GIT_REPOSITORY "https://github.com/ThePhD/sol2.git" CACHE STRING "sol2 repository URL")
set(LUABRIDGE_SOL2_GIT_TAG "v3.3.0" CACHE STRING "sol2 git tag or commit")
set(LUABRIDGE_BENCHMARK_WITH_LUABRIDGE ON CACHE BOOL "Build LuaBridge benchmark target")
set(LUABRIDGE_VANILLA_GIT_REPOSITORY "https://github.com/vinniefalco/LuaBridge.git" CACHE STRING "LuaBridge vanilla repository URL")
set(LUABRIDGE_VANILLA_GIT_TAG "master" CACHE STRING "LuaBridge vanilla git tag or commit")
set(LUABRIDGE_GOOGLE_BENCHMARK_GIT_REPOSITORY "https://github.com/google/benchmark.git" CACHE STRING "Google Benchmark repository URL")
set(LUABRIDGE_GOOGLE_BENCHMARK_GIT_TAG "v1.8.4" CACHE STRING "Google Benchmark git tag or commit")
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
googlebenchmark
GIT_REPOSITORY ${LUABRIDGE_GOOGLE_BENCHMARK_GIT_REPOSITORY}
GIT_TAG ${LUABRIDGE_GOOGLE_BENCHMARK_GIT_TAG})
FetchContent_MakeAvailable(googlebenchmark)
if (LUABRIDGE_BENCHMARK_WITH_SOL3)
FetchContent_Declare(
sol2
GIT_REPOSITORY ${LUABRIDGE_SOL2_GIT_REPOSITORY}
GIT_TAG ${LUABRIDGE_SOL2_GIT_TAG})
FetchContent_GetProperties(sol2)
if (NOT sol2_POPULATED)
FetchContent_Populate(sol2)
endif()
# Work around a sol2 optional<T&>::emplace bug on recent Apple Clang toolchains.
set(SOL2_OPTIONAL_IMPL "${sol2_SOURCE_DIR}/include/sol/optional_implementation.hpp")
if (EXISTS "${SOL2_OPTIONAL_IMPL}")
file(READ "${SOL2_OPTIONAL_IMPL}" SOL2_OPTIONAL_IMPL_CONTENT)
set(SOL2_OPTIONAL_REFBLOCK_OLD "\t\ttemplate <class... Args>\n\t\tT& emplace(Args&&... args) noexcept {\n\t\t\tstatic_assert(std::is_constructible<T, Args&&...>::value, \"T must be constructible with Args\");\n\n\t\t\t*this = nullopt;\n\t\t\tthis->construct(std::forward<Args>(args)...);\n\t\t}\n")
string(FIND "${SOL2_OPTIONAL_IMPL_CONTENT}" "${SOL2_OPTIONAL_REFBLOCK_OLD}" SOL2_PATCH_NEEDLE_POS)
if (NOT SOL2_PATCH_NEEDLE_POS EQUAL -1)
set(SOL2_OPTIONAL_REFBLOCK_NEW "\t\ttemplate <class... Args>\n\t\tT& emplace(Args&&... args) noexcept {\n\t\t\tstatic_assert(std::is_constructible<T, Args&&...>::value, \"T must be constructible with Args\");\n\n\t\t\t*this = nullopt;\n\t\t\tint emplace_workaround[] = { 0, ((*this = std::forward<Args>(args)), 0)... };\n\t\t\t(void) emplace_workaround;\n\t\t\treturn *m_value;\n\t\t}\n")
string(REPLACE
"${SOL2_OPTIONAL_REFBLOCK_OLD}"
"${SOL2_OPTIONAL_REFBLOCK_NEW}"
SOL2_OPTIONAL_IMPL_CONTENT
"${SOL2_OPTIONAL_IMPL_CONTENT}")
file(WRITE "${SOL2_OPTIONAL_IMPL}" "${SOL2_OPTIONAL_IMPL_CONTENT}")
endif()
endif()
endif()
if (LUABRIDGE_BENCHMARK_WITH_LUABRIDGE)
FetchContent_Declare(
luabridge_vanilla
GIT_REPOSITORY ${LUABRIDGE_VANILLA_GIT_REPOSITORY}
GIT_TAG ${LUABRIDGE_VANILLA_GIT_TAG})
FetchContent_GetProperties(luabridge_vanilla)
if (NOT luabridge_vanilla_POPULATED)
FetchContent_Populate(luabridge_vanilla)
endif()
endif()
function(add_luabridge_benchmark_target target_name source_file)
add_executable(${target_name}
${source_file}
benchmark_common.cpp
../Tests/Lua/LuaLibrary5.4.8.cpp)
target_include_directories(${target_name} PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/..
${CMAKE_CURRENT_LIST_DIR}/../Tests
${CMAKE_CURRENT_LIST_DIR}/../Tests/Lua/Lua.5.4.8/src)
target_compile_definitions(${target_name} PRIVATE
LUABRIDGE_BENCHMARK_LUA54=1
LUABRIDGE_TEST_LUA_VERSION=504)
target_link_libraries(${target_name} PRIVATE
benchmark::benchmark
benchmark::benchmark_main)
endfunction()
add_luabridge_benchmark_target(LuaBridge3Benchmark benchmark_luabridge3.cpp)
target_include_directories(LuaBridge3Benchmark PRIVATE
${CMAKE_CURRENT_LIST_DIR}/../Source)
if (LUABRIDGE_BENCHMARK_WITH_LUABRIDGE)
add_luabridge_benchmark_target(LuaBridgeVanillaBenchmark benchmark_luabridge.cpp)
target_include_directories(LuaBridgeVanillaBenchmark PRIVATE
${luabridge_vanilla_SOURCE_DIR}/Source)
endif()
if (LUABRIDGE_BENCHMARK_WITH_SOL3)
add_luabridge_benchmark_target(Sol3Benchmark benchmark_sol3.cpp)
target_include_directories(Sol3Benchmark PRIVATE
${CMAKE_CURRENT_LIST_DIR}/../Source
${sol2_SOURCE_DIR}/include)
target_compile_definitions(Sol3Benchmark PRIVATE
SOL_ALL_SAFETIES_ON=1
SOL_NO_EXCEPTIONS=1
SOL_LUA_VERSION=504)
endif()
+84
View File
@@ -0,0 +1,84 @@
# Lua Binding Benchmarks
This directory contains Google Benchmark based executables for:
- LuaBridge3 (current workspace)
- LuaBridge vanilla (`https://github.com/vinniefalco/LuaBridge`)
- sol3 (`https://github.com/ThePhD/sol2`)
All benchmark executables are built with the same embedded Lua 5.4.8 runtime source (`Tests/Lua/LuaLibrary5.4.8.cpp`) for fair comparisons.
## Build
From project root:
```bash
cmake -S . -B Build -DCMAKE_BUILD_TYPE=Release -DLUABRIDGE_BENCHMARKS=ON
cmake --build Build --config Release --target LuaBridge3Benchmark LuaBridgeVanillaBenchmark
```
To also build Sol3 benchmark target:
```bash
cmake -S . -B Build -DCMAKE_BUILD_TYPE=Release -DLUABRIDGE_BENCHMARKS=ON -DLUABRIDGE_BENCHMARK_WITH_SOL3=ON
cmake --build Build --config Release --target Sol3Benchmark
```
## Dependency Sources (FetchContent)
Defaults:
- Google Benchmark: `https://github.com/google/benchmark.git` (`v1.8.4`)
- sol3: `https://github.com/ThePhD/sol2.git` (`v3.5.0`)
- LuaBridge vanilla: `https://github.com/vinniefalco/LuaBridge.git` (`master`)
You can override these at configure time:
```bash
cmake -S . -B Build \
-DCMAKE_BUILD_TYPE=Release \
-DLUABRIDGE_BENCHMARKS=ON \
-DLUABRIDGE_SOL2_GIT_REPOSITORY=https://github.com/ThePhD/sol2.git \
-DLUABRIDGE_SOL2_GIT_TAG=v3.5.0 \
-DLUABRIDGE_VANILLA_GIT_REPOSITORY=https://github.com/vinniefalco/LuaBridge.git \
-DLUABRIDGE_VANILLA_GIT_TAG=master \
-DLUABRIDGE_GOOGLE_BENCHMARK_GIT_REPOSITORY=https://github.com/google/benchmark.git \
-DLUABRIDGE_GOOGLE_BENCHMARK_GIT_TAG=v1.8.4
```
## Run Benchmarks
Each executable supports standard Google Benchmark CLI flags.
```bash
./Build/Benchmarks/LuaBridge3Benchmark --benchmark_out=Build/Benchmarks/luabridge3.json --benchmark_out_format=json
./Build/Benchmarks/LuaBridgeVanillaBenchmark --benchmark_out=Build/Benchmarks/luabridge_vanilla.json --benchmark_out_format=json
./Build/Benchmarks/Sol3Benchmark --benchmark_out=Build/Benchmarks/sol3.json --benchmark_out_format=json # if enabled
```
Recommended consistency flags for fair comparison:
```bash
--benchmark_min_time=0.1 --benchmark_repetitions=5
```
## Plot Results
The script `plot_benchmarks.py` merges one or more Google Benchmark JSON files and generates a grouped comparison chart.
```bash
python3 Benchmarks/plot_benchmarks.py \
--input Build/Benchmarks/luabridge3.json Build/Benchmarks/luabridge_vanilla.json Build/Benchmarks/sol3.json \
--output Build/Benchmarks/lua_bindings_comparison.png
```
Outputs:
- PNG chart (grouped bars, lower is better)
- Optional skipped/error report file next to the image (`*_skipped.txt`)
## Notes
- Some vanilla LuaBridge benchmarks are marked as skipped where the feature is unsupported.
- Sol3 target is optional (`LUABRIDGE_BENCHMARK_WITH_SOL3`) because current sol2 headers can fail to compile on some toolchains.
- If you need stricter reproducibility, pin all FetchContent dependencies to commits instead of branches.
+31
View File
@@ -0,0 +1,31 @@
// https://github.com/kunitoki/LuaBridge3
// Copyright 2026, kunitoki
// Inspired from https://github.com/ThePhD/lua-bindings-shootout by ThePhD
// SPDX-License-Identifier: MIT
#include "benchmark_common.hpp"
#include <string>
namespace lbsbench {
void luaCheckOrThrow(lua_State* L, int status, std::string_view where)
{
if (status == LUA_OK)
return;
const char* message = lua_tostring(L, -1);
std::string error(where);
error += ": ";
error += (message ? message : "unknown lua error");
lua_pop(L, 1);
throw std::runtime_error(error);
}
void luaDoStringOrThrow(lua_State* L, std::string_view code, std::string_view where)
{
const int status = luaL_dostring(L, std::string(code).c_str());
luaCheckOrThrow(L, status, where);
}
} // namespace lbsbench
+232
View File
@@ -0,0 +1,232 @@
// https://github.com/kunitoki/LuaBridge3
// Copyright 2026, kunitoki
// Inspired from https://github.com/ThePhD/lua-bindings-shootout by ThePhD
// SPDX-License-Identifier: MIT
#pragma once
#include "Lua/LuaLibrary.h"
#include <benchmark/benchmark.h>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
namespace lbsbench {
inline constexpr double kMagicValue = 24.0;
struct Counter
{
int value = 0;
void inc()
{
++value;
}
int add(int x)
{
value += x;
return value;
}
int get() const
{
return value;
}
void set(int v)
{
value = v;
}
static int static_add(int a, int b)
{
return a + b;
}
};
struct Basic
{
double var = 0.0;
double get() const
{
return var;
}
void set(double v)
{
var = v;
}
};
struct BasicLarge
{
std::int64_t var = 0;
std::int64_t var0 = 0;
std::int64_t var1 = 0;
std::int64_t var2 = 0;
std::int64_t var3 = 0;
std::int64_t var4 = 0;
std::int64_t var5 = 0;
std::int64_t var6 = 0;
std::int64_t var7 = 0;
std::int64_t var8 = 0;
std::int64_t var9 = 0;
std::int64_t var10 = 0;
std::int64_t var11 = 0;
std::int64_t var12 = 0;
std::int64_t var13 = 0;
std::int64_t var14 = 0;
std::int64_t var15 = 0;
std::int64_t var16 = 0;
std::int64_t var17 = 0;
std::int64_t var18 = 0;
std::int64_t var19 = 0;
std::int64_t var20 = 0;
std::int64_t var21 = 0;
std::int64_t var22 = 0;
std::int64_t var23 = 0;
std::int64_t var24 = 0;
std::int64_t var25 = 0;
std::int64_t var26 = 0;
std::int64_t var27 = 0;
std::int64_t var28 = 0;
std::int64_t var29 = 0;
std::int64_t var30 = 0;
std::int64_t var31 = 0;
std::int64_t var32 = 0;
std::int64_t var33 = 0;
std::int64_t var34 = 0;
std::int64_t var35 = 0;
std::int64_t var36 = 0;
std::int64_t var37 = 0;
std::int64_t var38 = 0;
std::int64_t var39 = 0;
std::int64_t var40 = 0;
std::int64_t var41 = 0;
std::int64_t var42 = 0;
std::int64_t var43 = 0;
std::int64_t var44 = 0;
std::int64_t var45 = 0;
std::int64_t var46 = 0;
std::int64_t var47 = 0;
std::int64_t var48 = 0;
std::int64_t var49 = 0;
};
struct ComplexBaseA
{
double a = kMagicValue;
double a_func() const
{
return a;
}
};
struct ComplexBaseB
{
double b = kMagicValue;
double b_func() const
{
return b;
}
};
struct ComplexAB : ComplexBaseA, ComplexBaseB
{
double ab = kMagicValue;
double ab_func() const
{
return ab;
}
};
struct StatefulFunction
{
double operator()(double v) const
{
return v;
}
};
struct SharedObject : std::enable_shared_from_this<SharedObject>
{
double value = kMagicValue;
double get() const
{
return value;
}
};
struct Vec3Source
{
float x = 0.f, y = 0.f, z = 0.f;
Vec3Source() = default;
Vec3Source(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}
};
struct Vec3Target
{
float x = 0.f, y = 0.f, z = 0.f;
Vec3Target() = default;
Vec3Target(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}
};
struct ColorSource
{
float r = 0.f, g = 0.f, b = 0.f;
ColorSource() = default;
ColorSource(float r_, float g_, float b_) : r(r_), g(g_), b(b_) {}
};
inline float sumVec3(Vec3Target v)
{
return v.x + v.y + v.z;
}
inline float sumVec3Ref(const Vec3Target& v)
{
return v.x + v.y + v.z;
}
inline Basic* basic_return()
{
static Basic value{};
return &value;
}
inline double basic_get_var(Basic* b)
{
return b ? b->var : 0.0;
}
inline std::shared_ptr<SharedObject> shared_object_return()
{
static std::shared_ptr<SharedObject> obj = std::make_shared<SharedObject>();
return obj;
}
inline double shared_object_get_value(std::shared_ptr<SharedObject> obj)
{
return obj ? obj->get() : 0.0;
}
void luaCheckOrThrow(lua_State* L, int status, std::string_view where);
void luaDoStringOrThrow(lua_State* L, std::string_view code, std::string_view where);
inline void setSkipped(benchmark::State& state, std::string_view reason)
{
state.SkipWithError(std::string(reason).c_str());
}
} // namespace lbsbench
+514
View File
@@ -0,0 +1,514 @@
// https://github.com/kunitoki/LuaBridge3
// Copyright 2026, kunitoki
// Inspired from https://github.com/ThePhD/lua-bindings-shootout by ThePhD
// SPDX-License-Identifier: MIT
#include "benchmark_common.hpp"
#include <LuaBridge/LuaBridge.h>
#include <benchmark/benchmark.h>
namespace {
using namespace lbsbench;
int vanilla_multi_return(lua_State* L)
{
const double i = lua_tonumber(L, 1);
luabridge::push(L, i);
luabridge::push(L, i * 2.0);
return 2;
}
void registerBasicGetterSetter(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Basic>("c")
.addConstructor<void (*)()>()
.addProperty("val", &Basic::get, &Basic::set)
.endClass();
}
void registerCounter(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Counter>("Counter")
.addConstructor<void (*)()>()
.addFunction("get", &Counter::get)
.addStaticFunction("static_add", &Counter::static_add)
.endClass();
}
lua_State* makeLua()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
return L;
}
void registerBasic(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Basic>("c")
.addConstructor<void (*)()>()
.addFunction("set", &Basic::set)
.addFunction("get", &Basic::get)
.addData("var", &Basic::var)
.endClass();
}
void table_global_string_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::setGlobal(L, kMagicValue, "value");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(luabridge::getGlobal(L, "value"));
}
benchmark::DoNotOptimize(x);
}
void table_global_string_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
luabridge::setGlobal(L, v, "value");
}
benchmark::DoNotOptimize(v);
}
void table_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "vanilla table_get setup");
luabridge::LuaRef t = luabridge::getGlobal(L, "warble");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(t["value"]);
}
benchmark::DoNotOptimize(x);
}
void table_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "vanilla table_set setup");
luabridge::LuaRef t = luabridge::getGlobal(L, "warble");
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
t["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void table_chained_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "vanilla chained_get setup");
double x = 0;
for (auto _ : state)
{
(void) _;
luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"];
x += static_cast<double>(tw["value"]);
}
benchmark::DoNotOptimize(x);
}
void table_chained_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "vanilla chained_set setup");
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"];
tw["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void c_function_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", +[](double v) { return v; });
luaDoStringOrThrow(L, "function invoke_f() return f(24.0) end", "vanilla c_function setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_f");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_f");
lua_pop(L, 1);
}
}
void lua_function_in_c_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "function f(i) return i end", "vanilla lua_function setup");
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(f(kMagicValue));
}
benchmark::DoNotOptimize(x);
}
void c_function_through_lua_in_c_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", +[](double v) { return v; });
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(f(kMagicValue));
}
benchmark::DoNotOptimize(x);
}
void member_function_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luaDoStringOrThrow(L, "b = c()", "vanilla member setup");
luaDoStringOrThrow(L, "function call_member() b:set(b:get() + 1.0) end", "vanilla member closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "call_member");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla call_member");
}
}
void userdata_variable_access_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luaDoStringOrThrow(L, "b = c()", "vanilla userdata setup");
luaDoStringOrThrow(L, "function access_var() b.var = b.var + 1.0 return b.var end", "vanilla userdata closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "access_var");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla access_var");
lua_pop(L, 1);
}
}
void userdata_variable_access_large_measure(benchmark::State& state)
{
setSkipped(state, "unsupported in LuaBridge vanilla benchmark parity mode");
}
void userdata_variable_access_last_measure(benchmark::State& state)
{
setSkipped(state, "unsupported in LuaBridge vanilla benchmark parity mode");
}
void stateful_function_object_measure(benchmark::State& state)
{
setSkipped(state, "unsupported in LuaBridge vanilla benchmark parity mode");
}
void multi_return_lua_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addCFunction("f", vanilla_multi_return);
luaDoStringOrThrow(L, "function invoke_multi() local a,b=f(24.0) return a+b end", "vanilla multi_return_lua setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_multi");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_multi");
lua_pop(L, 1);
}
}
void multi_return_measure(benchmark::State& state)
{
setSkipped(state, "unsupported conceptual multi-return conversion in LuaBridge vanilla");
}
void derived_base_measure(benchmark::State& state)
{
setSkipped(state, "unsupported for multi inheritance in LuaBridge vanilla");
}
void return_userdata_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luabridge::getGlobalNamespace(L)
.addFunction("f", &basic_return)
.addFunction("h", &basic_get_var);
luaDoStringOrThrow(L, "function invoke_userdata() return h(f()) end", "vanilla return_userdata setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_userdata");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_userdata");
lua_pop(L, 1);
}
}
void optional_success_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "vanilla optional_success setup");
double x = 0;
for (auto _ : state)
{
(void) _;
luabridge::LuaRef tt = luabridge::getGlobal(L, "warble");
if (tt.isTable())
{
luabridge::LuaRef tv = tt["value"];
x += tv.isNumber() ? static_cast<double>(tv) : 1.0;
}
else
{
x += 1.0;
}
}
benchmark::DoNotOptimize(x);
}
void optional_half_failure_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 'x' }", "vanilla optional_half_failure setup");
double x = 0;
for (auto _ : state)
{
(void) _;
luabridge::LuaRef tt = luabridge::getGlobal(L, "warble");
if (tt.isTable())
{
luabridge::LuaRef tv = tt["value"];
x += tv.isNumber() ? static_cast<double>(tv) : 1.0;
}
else
{
x += 1.0;
}
}
benchmark::DoNotOptimize(x);
}
void optional_failure_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double x = 0;
for (auto _ : state)
{
(void) _;
luabridge::LuaRef tt = luabridge::getGlobal(L, "warble");
if (tt.isTable())
{
luabridge::LuaRef tv = tt["value"];
x += tv.isNumber() ? static_cast<double>(tv) : 1.0;
}
else
{
x += 1.0;
}
}
benchmark::DoNotOptimize(x);
}
void userdata_variable_write_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luaDoStringOrThrow(L, "b = c()", "vanilla userdata_write setup");
luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "vanilla userdata_write closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "write_var");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_var");
}
}
void userdata_property_getter_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicGetterSetter(L);
luaDoStringOrThrow(L, "b = c()", "vanilla property_getter setup");
luaDoStringOrThrow(L, "function read_getter() return b.val end", "vanilla property_getter closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "read_getter");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla read_getter");
lua_pop(L, 1);
}
}
void userdata_property_setter_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicGetterSetter(L);
luaDoStringOrThrow(L, "b = c()", "vanilla property_setter setup");
luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "vanilla property_setter closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "write_setter");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_setter");
}
}
void lambda_capture_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double extra = kMagicValue;
luabridge::getGlobalNamespace(L).addFunction("f", std::function<double(double)>([extra](double v) { return v + extra; }));
luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "vanilla lambda_capture setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_lambda");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_lambda");
lua_pop(L, 1);
}
}
void shared_ptr_return_measure(benchmark::State& state)
{
setSkipped(state, "unsupported shared_ptr container in LuaBridge vanilla");
}
void shared_ptr_pass_measure(benchmark::State& state)
{
setSkipped(state, "unsupported shared_ptr container in LuaBridge vanilla");
}
void static_member_function_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerCounter(L);
luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "vanilla static_member_function setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_static");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_static");
lua_pop(L, 1);
}
}
void derived_method_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L)
.beginClass<ComplexBaseA>("ComplexBaseA")
.addFunction("a_func", &ComplexBaseA::a_func)
.addProperty("a", &ComplexBaseA::a)
.endClass()
.deriveClass<ComplexAB, ComplexBaseA>("ComplexAB")
.addConstructor<void (*)()>()
.addFunction("ab_func", &ComplexAB::ab_func)
.addProperty("ab", &ComplexAB::ab)
.endClass();
luaDoStringOrThrow(L, "obj = ComplexAB()", "vanilla derived_method setup");
luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "vanilla derived_method closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "call_derived");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla call_derived");
lua_pop(L, 1);
}
}
void implicit_inheritance_measure(benchmark::State& state)
{
setSkipped(state, "unsupported for multi inheritance in LuaBridge vanilla");
}
} // namespace
BENCHMARK(table_global_string_get_measure)->Name("table_global_string_get_measure");
BENCHMARK(table_global_string_set_measure)->Name("table_global_string_set_measure");
BENCHMARK(table_get_measure)->Name("table_get_measure");
BENCHMARK(table_set_measure)->Name("table_set_measure");
BENCHMARK(table_chained_get_measure)->Name("table_chained_get_measure");
BENCHMARK(table_chained_set_measure)->Name("table_chained_set_measure");
BENCHMARK(c_function_measure)->Name("c_function_measure");
BENCHMARK(c_function_through_lua_in_c_measure)->Name("c_function_through_lua_in_c_measure");
BENCHMARK(lua_function_in_c_measure)->Name("lua_function_in_c_measure");
BENCHMARK(member_function_call_measure)->Name("member_function_call_measure");
BENCHMARK(userdata_variable_access_measure)->Name("userdata_variable_access_measure");
BENCHMARK(userdata_variable_access_large_measure)->Name("userdata_variable_access_large_measure");
BENCHMARK(userdata_variable_access_last_measure)->Name("userdata_variable_access_last_measure");
BENCHMARK(multi_return_lua_measure)->Name("multi_return_lua_measure");
BENCHMARK(multi_return_measure)->Name("multi_return_measure");
BENCHMARK(stateful_function_object_measure)->Name("stateful_function_object_measure");
BENCHMARK(derived_base_measure)->Name("derived_base_measure");
BENCHMARK(return_userdata_measure)->Name("return_userdata_measure");
BENCHMARK(optional_success_measure)->Name("optional_success_measure");
BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure");
BENCHMARK(optional_failure_measure)->Name("optional_failure_measure");
BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure");
BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure");
BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure");
BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure");
BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure");
BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure");
BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure");
BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure");
BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure");
+805
View File
@@ -0,0 +1,805 @@
// https://github.com/kunitoki/LuaBridge3
// Copyright 2026, kunitoki
// Inspired from https://github.com/ThePhD/lua-bindings-shootout by ThePhD
// SPDX-License-Identifier: MIT
#include "benchmark_common.hpp"
#include "LuaBridge/LuaBridge.h"
#include <benchmark/benchmark.h>
#include <cmath>
#include <tuple>
namespace luabridge {
template <>
struct StackConversion<lbsbench::Vec3Target>
{
static constexpr bool enabled = true;
};
template <>
struct StackConverter<lbsbench::Vec3Target, lbsbench::Vec3Source>
{
static lbsbench::Vec3Target convert(const lbsbench::Vec3Source& s)
{
return {s.x, s.y, s.z};
}
};
template <>
struct StackConverter<lbsbench::Vec3Target, lbsbench::ColorSource>
{
static lbsbench::Vec3Target convert(const lbsbench::ColorSource& s)
{
return {s.r, s.g, s.b};
}
};
} // namespace luabridge
namespace {
using namespace lbsbench;
std::tuple<double, double> lb3_multi_return(double value)
{
return { value, value * 2.0 };
}
void registerBasic(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Basic>("c")
.addConstructor<void (*)()>()
.addFunction("set", &Basic::set)
.addFunction("get", &Basic::get)
.addProperty("var", &Basic::var)
.endClass();
}
void registerBasicLarge(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<BasicLarge>("cl")
.addConstructor<void (*)()>()
.addProperty("var", &BasicLarge::var)
.addProperty("var0", &BasicLarge::var0)
.addProperty("var1", &BasicLarge::var1)
.addProperty("var2", &BasicLarge::var2)
.addProperty("var3", &BasicLarge::var3)
.addProperty("var4", &BasicLarge::var4)
.addProperty("var5", &BasicLarge::var5)
.addProperty("var6", &BasicLarge::var6)
.addProperty("var7", &BasicLarge::var7)
.addProperty("var8", &BasicLarge::var8)
.addProperty("var9", &BasicLarge::var9)
.addProperty("var10", &BasicLarge::var10)
.addProperty("var11", &BasicLarge::var11)
.addProperty("var12", &BasicLarge::var12)
.addProperty("var13", &BasicLarge::var13)
.addProperty("var14", &BasicLarge::var14)
.addProperty("var15", &BasicLarge::var15)
.addProperty("var16", &BasicLarge::var16)
.addProperty("var17", &BasicLarge::var17)
.addProperty("var18", &BasicLarge::var18)
.addProperty("var19", &BasicLarge::var19)
.addProperty("var20", &BasicLarge::var20)
.addProperty("var21", &BasicLarge::var21)
.addProperty("var22", &BasicLarge::var22)
.addProperty("var23", &BasicLarge::var23)
.addProperty("var24", &BasicLarge::var24)
.addProperty("var25", &BasicLarge::var25)
.addProperty("var26", &BasicLarge::var26)
.addProperty("var27", &BasicLarge::var27)
.addProperty("var28", &BasicLarge::var28)
.addProperty("var29", &BasicLarge::var29)
.addProperty("var30", &BasicLarge::var30)
.addProperty("var31", &BasicLarge::var31)
.addProperty("var32", &BasicLarge::var32)
.addProperty("var33", &BasicLarge::var33)
.addProperty("var34", &BasicLarge::var34)
.addProperty("var35", &BasicLarge::var35)
.addProperty("var36", &BasicLarge::var36)
.addProperty("var37", &BasicLarge::var37)
.addProperty("var38", &BasicLarge::var38)
.addProperty("var39", &BasicLarge::var39)
.addProperty("var40", &BasicLarge::var40)
.addProperty("var41", &BasicLarge::var41)
.addProperty("var42", &BasicLarge::var42)
.addProperty("var43", &BasicLarge::var43)
.addProperty("var44", &BasicLarge::var44)
.addProperty("var45", &BasicLarge::var45)
.addProperty("var46", &BasicLarge::var46)
.addProperty("var47", &BasicLarge::var47)
.addProperty("var48", &BasicLarge::var48)
.addProperty("var49", &BasicLarge::var49)
.endClass();
}
void registerBasicRW(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Basic>("c")
.addConstructor<void (*)()>()
.addPropertyReadWrite("var", &Basic::var)
.endClass();
}
void registerBasicGetterSetter(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Basic>("c")
.addConstructor<void (*)()>()
.addProperty("val", &Basic::get, &Basic::set)
.endClass();
}
void registerCounter(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Counter>("Counter")
.addConstructor<void (*)()>()
.addFunction("get", &Counter::get)
.addStaticFunction("static_add", &Counter::static_add)
.endClass();
}
void registerSharedObject(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<SharedObject>("SharedObject")
.addConstructorFrom<std::shared_ptr<SharedObject>, void(*)()>()
.addFunction("get", &SharedObject::get)
.endClass()
.addFunction("get_shared", &shared_object_return)
.addFunction("use_shared", &shared_object_get_value);
}
lua_State* makeLua()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luabridge::registerMainThread(L);
#if LUABRIDGE_HAS_EXCEPTIONS
luabridge::enableExceptions(L);
#endif
return L;
}
void table_global_string_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::setGlobal(L, kMagicValue, "value");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(luabridge::getGlobal(L, "value"));
}
benchmark::DoNotOptimize(x);
}
void table_global_string_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
luabridge::setGlobal(L, v, "value");
}
benchmark::DoNotOptimize(v);
}
void table_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "table_get setup");
luabridge::LuaRef t = luabridge::getGlobal(L, "warble");
double x = 0;
for (auto _ : state)
{
(void) _;
x += static_cast<double>(t["value"]);
}
benchmark::DoNotOptimize(x);
}
void table_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "table_set setup");
luabridge::LuaRef t = luabridge::getGlobal(L, "warble");
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
t["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void table_chained_get_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "table_chained_get setup");
double x = 0;
for (auto _ : state)
{
(void) _;
luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"];
x += static_cast<double>(tw["value"]);
}
benchmark::DoNotOptimize(x);
}
void table_chained_set_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "ulahibe = { warble = { value = 24.0 } }", "table_chained_set setup");
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
luabridge::LuaRef tw = luabridge::getGlobal(L, "ulahibe")["warble"];
tw["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void c_function_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", +[](double v) { return v; });
luaDoStringOrThrow(L, "function invoke_f() return f(24.0) end", "c_function setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_f");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_f");
lua_pop(L, 1);
}
}
void lua_function_in_c_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "function f(i) return i end", "lua_function setup");
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue).valueOr(0.0);
}
benchmark::DoNotOptimize(x);
}
void c_function_through_lua_in_c_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", +[](double v) { return v; });
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue).valueOr(0.0);
}
benchmark::DoNotOptimize(x);
}
void member_function_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luaDoStringOrThrow(L, "b = c()", "member_function setup");
luaDoStringOrThrow(L, "function call_member() b:set(b:get() + 1.0) end", "member_function closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "call_member");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "call_member");
}
}
void userdata_variable_access_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luaDoStringOrThrow(L, "b = c()", "userdata_variable_access setup");
luaDoStringOrThrow(L, "function access_var() return b.var end", "userdata_variable_access closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "access_var");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var");
lua_pop(L, 1);
}
}
void userdata_variable_access_large_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicLarge(L);
luaDoStringOrThrow(L, "b = cl()", "userdata_variable_access_large setup");
luaDoStringOrThrow(L, "function access_var_large() return b.var0 end", "userdata_variable_access_large closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "access_var_large");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var_large");
lua_pop(L, 1);
}
}
void userdata_variable_access_last_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicLarge(L);
luaDoStringOrThrow(L, "b = cl()", "userdata_variable_access_last setup");
luaDoStringOrThrow(L, "function access_var_last() return b.var49 end", "userdata_variable_access_last closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "access_var_last");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "access_var_last");
lua_pop(L, 1);
}
}
void stateful_function_object_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", StatefulFunction{});
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue).valueOr(0.0);
}
benchmark::DoNotOptimize(x);
}
void multi_return_lua_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", &lb3_multi_return);
luaDoStringOrThrow(L, "function invoke_multi() local a,b=f(24.0) return a+b end", "multi_return_lua setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_multi");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_multi");
lua_pop(L, 1);
}
}
void multi_return_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L).addFunction("f", &lb3_multi_return);
luabridge::LuaRef f = luabridge::getGlobal(L, "f");
double x = 0;
for (auto _ : state)
{
(void) _;
auto result = f.call<std::tuple<double, double>>(kMagicValue).valueOr(std::make_tuple(0.0, 0.0));
x += std::get<0>(result);
x += std::get<1>(result);
}
benchmark::DoNotOptimize(x);
}
void derived_base_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L)
.beginClass<ComplexBaseA>("ComplexBaseA")
.addFunction("a_func", &ComplexBaseA::a_func)
.addProperty("a", &ComplexBaseA::a)
.endClass()
.beginClass<ComplexBaseB>("ComplexBaseB")
.addFunction("b_func", &ComplexBaseB::b_func)
.addProperty("b", &ComplexBaseB::b)
.endClass()
.deriveClass<ComplexAB, ComplexBaseA, ComplexBaseB>("ComplexAB")
.addConstructor<void (*)()>()
.addFunction("ab_func", &ComplexAB::ab_func)
.addProperty("ab", &ComplexAB::ab)
.endClass();
luaDoStringOrThrow(L, "obj = ComplexAB()", "base_derived setup");
luaDoStringOrThrow(L, "function call_base() return obj:a_func() + obj:b_func() end", "base_derived closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "call_base");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "call_base");
lua_pop(L, 1);
}
}
void optional_success_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 24.0 }", "optional_success setup");
double x = 0;
for (auto _ : state)
{
(void) _;
auto result = luabridge::tryGetGlobalField<double>(L, "warble", "value");
x += result ? *result : 1.0;
}
benchmark::DoNotOptimize(x);
}
void optional_half_failure_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luaDoStringOrThrow(L, "warble = { value = 'x' }", "optional_half_failure setup");
double x = 0;
for (auto _ : state)
{
(void) _;
auto result = luabridge::tryGetGlobalField<double>(L, "warble", "value");
x += result ? *result : 1.0;
}
benchmark::DoNotOptimize(x);
}
void optional_failure_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double x = 0;
for (auto _ : state)
{
(void) _;
auto result = luabridge::tryGetGlobalField<double>(L, "warble", "value");
x += result ? *result : 1.0;
}
benchmark::DoNotOptimize(x);
}
void return_userdata_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasic(L);
luabridge::getGlobalNamespace(L)
.addFunction("f", &basic_return)
.addFunction("h", &basic_get_var);
luaDoStringOrThrow(L, "function invoke_userdata() return h(f()) end", "return_userdata setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_userdata");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_userdata");
lua_pop(L, 1);
}
}
void userdata_variable_write_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicRW(L);
luaDoStringOrThrow(L, "b = c()", "userdata_variable_write setup");
luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "userdata_variable_write closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "write_var");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_var");
}
}
void userdata_property_getter_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicGetterSetter(L);
luaDoStringOrThrow(L, "b = c()", "userdata_property_getter setup");
luaDoStringOrThrow(L, "function read_getter() return b.val end", "userdata_property_getter closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "read_getter");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "read_getter");
lua_pop(L, 1);
}
}
void userdata_property_setter_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerBasicGetterSetter(L);
luaDoStringOrThrow(L, "b = c()", "userdata_property_setter setup");
luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "userdata_property_setter closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "write_setter");
luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_setter");
}
}
void lambda_capture_measure(benchmark::State& state)
{
lua_State* L = makeLua();
double extra = kMagicValue;
luabridge::getGlobalNamespace(L).addFunction("f", [extra](double v) { return v + extra; });
luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "lambda_capture setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_lambda");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_lambda");
lua_pop(L, 1);
}
}
void shared_ptr_return_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerSharedObject(L);
luaDoStringOrThrow(L, "function invoke_shared() return get_shared():get() end", "shared_ptr_return setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_shared");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_shared");
lua_pop(L, 1);
}
}
void shared_ptr_pass_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerSharedObject(L);
luaDoStringOrThrow(L, "obj = SharedObject()", "shared_ptr_pass setup");
luaDoStringOrThrow(L, "function invoke_pass_shared() return use_shared(obj) end", "shared_ptr_pass closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_pass_shared");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_pass_shared");
lua_pop(L, 1);
}
}
void static_member_function_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerCounter(L);
luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "static_member_function setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_static");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_static");
lua_pop(L, 1);
}
}
void derived_method_call_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L)
.beginClass<ComplexBaseA>("ComplexBaseA")
.addFunction("a_func", &ComplexBaseA::a_func)
.addProperty("a", &ComplexBaseA::a)
.endClass()
.beginClass<ComplexBaseB>("ComplexBaseB")
.addFunction("b_func", &ComplexBaseB::b_func)
.addProperty("b", &ComplexBaseB::b)
.endClass()
.deriveClass<ComplexAB, ComplexBaseA, ComplexBaseB>("ComplexAB")
.addConstructor<void (*)()>()
.addFunction("ab_func", &ComplexAB::ab_func)
.addProperty("ab", &ComplexAB::ab)
.endClass();
luaDoStringOrThrow(L, "obj = ComplexAB()", "derived_method setup");
luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "derived_method closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "call_derived");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "call_derived");
lua_pop(L, 1);
}
}
void implicit_inheritance_measure(benchmark::State& state)
{
lua_State* L = makeLua();
luabridge::getGlobalNamespace(L)
.beginClass<ComplexBaseA>("ComplexBaseA")
.addFunction("a_func", &ComplexBaseA::a_func)
.endClass()
.deriveClass<ComplexAB, ComplexBaseA>("ComplexAB")
.addConstructor<void (*)()>()
.addFunction("ab_func", &ComplexAB::ab_func)
.endClass()
.addFunction("call_a", +[](ComplexBaseA* obj) -> double { return obj->a_func(); });
luaDoStringOrThrow(L, "obj = ComplexAB()", "implicit_inheritance setup");
luaDoStringOrThrow(L, "function test_implicit() return call_a(obj) end", "implicit_inheritance closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "test_implicit");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "test_implicit");
lua_pop(L, 1);
}
}
void registerConverter(lua_State* L)
{
luabridge::getGlobalNamespace(L)
.beginClass<Vec3Source>("Vec3Source")
.addConstructor<void(float, float, float)>()
.addConverter<Vec3Target>()
.endClass()
.beginClass<ColorSource>("ColorSource")
.addConstructor<void(float, float, float)>()
.addConverter<Vec3Target>()
.endClass()
.beginClass<Vec3Target>("Vec3Target")
.addConstructor<void(float, float, float)>()
.endClass()
.addFunction("sumVec3", &sumVec3)
.addFunction("sumVec3Ref", &sumVec3Ref);
}
void converter_exact_type_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerConverter(L);
luaDoStringOrThrow(L, "obj = Vec3Target(1, 2, 3)", "converter_exact_type setup");
luaDoStringOrThrow(L, "function invoke_exact() return sumVec3(obj) end", "converter_exact_type closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_exact");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_exact");
lua_pop(L, 1);
}
}
void converter_phase3_value_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerConverter(L);
luaDoStringOrThrow(L, "obj = Vec3Source(1, 2, 3)", "converter_phase3_value setup");
luaDoStringOrThrow(L, "function invoke_conv_value() return sumVec3(obj) end", "converter_phase3_value closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_conv_value");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_value");
lua_pop(L, 1);
}
}
void converter_phase3_ref_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerConverter(L);
luaDoStringOrThrow(L, "obj = Vec3Source(1, 2, 3)", "converter_phase3_ref setup");
luaDoStringOrThrow(L, "function invoke_conv_ref() return sumVec3Ref(obj) end", "converter_phase3_ref closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_conv_ref");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_ref");
lua_pop(L, 1);
}
}
void converter_multi_registered_measure(benchmark::State& state)
{
lua_State* L = makeLua();
registerConverter(L);
luaDoStringOrThrow(L, "obj = ColorSource(0.5, 1, 0)", "converter_multi_registered setup");
luaDoStringOrThrow(L, "function invoke_conv_multi() return sumVec3(obj) end", "converter_multi_registered closure setup");
for (auto _ : state)
{
(void) _;
lua_getglobal(L, "invoke_conv_multi");
luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_conv_multi");
lua_pop(L, 1);
}
}
} // namespace
BENCHMARK(table_global_string_get_measure)->Name("table_global_string_get_measure");
BENCHMARK(table_global_string_set_measure)->Name("table_global_string_set_measure");
BENCHMARK(table_get_measure)->Name("table_get_measure");
BENCHMARK(table_set_measure)->Name("table_set_measure");
BENCHMARK(table_chained_get_measure)->Name("table_chained_get_measure");
BENCHMARK(table_chained_set_measure)->Name("table_chained_set_measure");
BENCHMARK(c_function_measure)->Name("c_function_measure");
BENCHMARK(c_function_through_lua_in_c_measure)->Name("c_function_through_lua_in_c_measure");
BENCHMARK(lua_function_in_c_measure)->Name("lua_function_in_c_measure");
BENCHMARK(member_function_call_measure)->Name("member_function_call_measure");
BENCHMARK(userdata_variable_access_measure)->Name("userdata_variable_access_measure");
BENCHMARK(userdata_variable_access_large_measure)->Name("userdata_variable_access_large_measure");
BENCHMARK(userdata_variable_access_last_measure)->Name("userdata_variable_access_last_measure");
BENCHMARK(multi_return_lua_measure)->Name("multi_return_lua_measure");
BENCHMARK(multi_return_measure)->Name("multi_return_measure");
BENCHMARK(stateful_function_object_measure)->Name("stateful_function_object_measure");
BENCHMARK(derived_base_measure)->Name("derived_base_measure");
BENCHMARK(return_userdata_measure)->Name("return_userdata_measure");
BENCHMARK(optional_success_measure)->Name("optional_success_measure");
BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure");
BENCHMARK(optional_failure_measure)->Name("optional_failure_measure");
BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure");
BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure");
BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure");
BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure");
BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure");
BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure");
BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure");
BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure");
BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure");
BENCHMARK(converter_exact_type_measure)->Name("converter_exact_type_measure");
BENCHMARK(converter_phase3_value_measure)->Name("converter_phase3_value_measure");
BENCHMARK(converter_phase3_ref_measure)->Name("converter_phase3_ref_measure");
BENCHMARK(converter_multi_registered_measure)->Name("converter_multi_registered_measure");
+618
View File
@@ -0,0 +1,618 @@
// https://github.com/kunitoki/LuaBridge3
// Copyright 2026, kunitoki
// Inspired from https://github.com/ThePhD/lua-bindings-shootout by ThePhD
// SPDX-License-Identifier: MIT
#include "benchmark_common.hpp"
#include <sol/sol.hpp>
#include <benchmark/benchmark.h>
#include <tuple>
namespace {
using namespace lbsbench;
std::tuple<double, double> sol3_multi_return(double value)
{
return { value, value * 2.0 };
}
void registerBasic(sol::state& lua)
{
lua.new_usertype<Basic>("c",
sol::constructors<Basic()>(),
"set", &Basic::set,
"get", &Basic::get,
"var", &Basic::var);
}
void registerBasicLarge(sol::state& lua)
{
lua.new_usertype<BasicLarge>("cl",
sol::constructors<BasicLarge()>(),
"var", &BasicLarge::var,
"var0", &BasicLarge::var0,
"var1", &BasicLarge::var1,
"var2", &BasicLarge::var2,
"var3", &BasicLarge::var3,
"var4", &BasicLarge::var4,
"var5", &BasicLarge::var5,
"var6", &BasicLarge::var6,
"var7", &BasicLarge::var7,
"var8", &BasicLarge::var8,
"var9", &BasicLarge::var9,
"var10", &BasicLarge::var10,
"var11", &BasicLarge::var11,
"var12", &BasicLarge::var12,
"var13", &BasicLarge::var13,
"var14", &BasicLarge::var14,
"var15", &BasicLarge::var15,
"var16", &BasicLarge::var16,
"var17", &BasicLarge::var17,
"var18", &BasicLarge::var18,
"var19", &BasicLarge::var19,
"var20", &BasicLarge::var20,
"var21", &BasicLarge::var21,
"var22", &BasicLarge::var22,
"var23", &BasicLarge::var23,
"var24", &BasicLarge::var24,
"var25", &BasicLarge::var25,
"var26", &BasicLarge::var26,
"var27", &BasicLarge::var27,
"var28", &BasicLarge::var28,
"var29", &BasicLarge::var29,
"var30", &BasicLarge::var30,
"var31", &BasicLarge::var31,
"var32", &BasicLarge::var32,
"var33", &BasicLarge::var33,
"var34", &BasicLarge::var34,
"var35", &BasicLarge::var35,
"var36", &BasicLarge::var36,
"var37", &BasicLarge::var37,
"var38", &BasicLarge::var38,
"var39", &BasicLarge::var39,
"var40", &BasicLarge::var40,
"var41", &BasicLarge::var41,
"var42", &BasicLarge::var42,
"var43", &BasicLarge::var43,
"var44", &BasicLarge::var44,
"var45", &BasicLarge::var45,
"var46", &BasicLarge::var46,
"var47", &BasicLarge::var47,
"var48", &BasicLarge::var48,
"var49", &BasicLarge::var49);
}
void registerBasicGetterSetter(sol::state& lua)
{
lua.new_usertype<Basic>("c",
sol::constructors<Basic()>(),
"val", sol::property(&Basic::get, &Basic::set));
}
void registerCounter(sol::state& lua)
{
lua.new_usertype<Counter>("Counter",
sol::constructors<Counter()>(),
"get", &Counter::get,
"static_add", &Counter::static_add);
}
void registerSharedObject(sol::state& lua)
{
lua.new_usertype<SharedObject>("SharedObject",
sol::call_constructor,
sol::factories([]() { return std::make_shared<SharedObject>(); }),
"get", &SharedObject::get);
lua.set_function("get_shared", &shared_object_return);
lua.set_function("use_shared", &shared_object_get_value);
}
void table_global_string_get_measure(benchmark::State& state)
{
sol::state lua;
lua["value"] = kMagicValue;
double x = 0;
for (auto _ : state)
{
(void) _;
x += lua["value"].get<double>();
}
benchmark::DoNotOptimize(x);
}
void table_global_string_set_measure(benchmark::State& state)
{
sol::state lua;
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
lua["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void table_get_measure(benchmark::State& state)
{
sol::state lua;
lua.script("warble = { value = 24.0 }");
sol::table t = lua["warble"];
double x = 0;
for (auto _ : state)
{
(void) _;
x += t["value"].get<double>();
}
benchmark::DoNotOptimize(x);
}
void table_set_measure(benchmark::State& state)
{
sol::state lua;
lua.script("warble = { value = 24.0 }");
sol::table t = lua["warble"];
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
t["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void table_chained_get_measure(benchmark::State& state)
{
sol::state lua;
lua.script("ulahibe = { warble = { value = 24.0 } }");
double x = 0;
for (auto _ : state)
{
(void) _;
x += lua["ulahibe"]["warble"]["value"].get<double>();
}
benchmark::DoNotOptimize(x);
}
void table_chained_set_measure(benchmark::State& state)
{
sol::state lua;
lua.script("ulahibe = { warble = { value = 24.0 } }");
double v = 0;
for (auto _ : state)
{
(void) _;
v += kMagicValue;
lua["ulahibe"]["warble"]["value"] = v;
}
benchmark::DoNotOptimize(v);
}
void c_function_measure(benchmark::State& state)
{
sol::state lua;
lua.set_function("f", +[](double value) { return value; });
lua.script("function invoke_f() return f(24.0) end");
for (auto _ : state)
{
(void) _;
lua["invoke_f"]();
}
}
void lua_function_in_c_measure(benchmark::State& state)
{
sol::state lua;
lua.script("function f(i) return i end");
sol::function f = lua["f"];
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue);
}
benchmark::DoNotOptimize(x);
}
void c_function_through_lua_in_c_measure(benchmark::State& state)
{
sol::state lua;
lua.set_function("f", +[](double value) { return value; });
sol::function f = lua["f"];
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue);
}
benchmark::DoNotOptimize(x);
}
void member_function_call_measure(benchmark::State& state)
{
sol::state lua;
registerBasic(lua);
lua.script("b = c.new()\nfunction call_member() b:set(b:get() + 1.0) end");
for (auto _ : state)
{
(void) _;
lua["call_member"]();
}
}
void userdata_variable_access_measure(benchmark::State& state)
{
sol::state lua;
registerBasic(lua);
lua.script("b = c.new()\nfunction access_var() b.var = b.var + 1.0 return b.var end");
for (auto _ : state)
{
(void) _;
lua["access_var"]();
}
}
void userdata_variable_access_large_measure(benchmark::State& state)
{
sol::state lua;
registerBasicLarge(lua);
lua.script("b = cl.new()\nfunction access_var_large() b.var0 = b.var0 + 1 return b.var0 end");
for (auto _ : state)
{
(void) _;
lua["access_var_large"]();
}
}
void userdata_variable_access_last_measure(benchmark::State& state)
{
sol::state lua;
registerBasicLarge(lua);
lua.script("b = cl.new()\nfunction access_var_last() b.var49 = b.var49 + 1 return b.var49 end");
for (auto _ : state)
{
(void) _;
lua["access_var_last"]();
}
}
void stateful_function_object_measure(benchmark::State& state)
{
sol::state lua;
lua.set_function("f", StatefulFunction{});
sol::function f = lua["f"];
double x = 0;
for (auto _ : state)
{
(void) _;
x += f.call<double>(kMagicValue);
}
benchmark::DoNotOptimize(x);
}
void multi_return_lua_measure(benchmark::State& state)
{
sol::state lua;
lua.set_function("f", &sol3_multi_return);
lua.script("function invoke_multi() local a,b=f(24.0) return a+b end");
for (auto _ : state)
{
(void) _;
lua["invoke_multi"]();
}
}
void multi_return_measure(benchmark::State& state)
{
sol::state lua;
lua.set_function("f", &sol3_multi_return);
sol::function f = lua["f"];
double x = 0;
for (auto _ : state)
{
(void) _;
std::tuple<double, double> values = f.call<double, double>(kMagicValue);
x += std::get<0>(values);
x += std::get<1>(values);
}
benchmark::DoNotOptimize(x);
}
void derived_base_measure(benchmark::State& state)
{
sol::state lua;
lua.new_usertype<ComplexBaseA>("ComplexBaseA",
"a_func", &ComplexBaseA::a_func,
"a", &ComplexBaseA::a);
lua.new_usertype<ComplexBaseB>("ComplexBaseB",
"b_func", &ComplexBaseB::b_func,
"b", &ComplexBaseB::b);
lua.new_usertype<ComplexAB>("ComplexAB",
sol::base_classes, sol::bases<ComplexBaseA, ComplexBaseB>(),
"ab_func", &ComplexAB::ab_func,
"ab", &ComplexAB::ab);
ComplexAB ab;
lua["b"] = &ab;
lua.script("function call_base() return b:a_func() + b:b_func() end");
for (auto _ : state)
{
(void) _;
lua["call_base"]();
}
}
void optional_success_measure(benchmark::State& state)
{
sol::state lua;
lua.script("warble = { value = 24.0 }");
double x = 0;
for (auto _ : state)
{
(void) _;
sol::optional<double> value = lua["warble"]["value"];
x += value.value_or(1.0);
}
benchmark::DoNotOptimize(x);
}
void optional_half_failure_measure(benchmark::State& state)
{
sol::state lua;
lua.script("warble = { value = 'x' }");
double x = 0;
for (auto _ : state)
{
(void) _;
sol::optional<double> value = lua["warble"]["value"];
x += value.value_or(1.0);
}
benchmark::DoNotOptimize(x);
}
void optional_failure_measure(benchmark::State& state)
{
sol::state lua;
double x = 0;
for (auto _ : state)
{
(void) _;
sol::optional<double> value = lua["warble"]["value"];
x += value.value_or(1.0);
}
benchmark::DoNotOptimize(x);
}
void return_userdata_measure(benchmark::State& state)
{
sol::state lua;
registerBasic(lua);
lua.set_function("f", &basic_return);
lua.set_function("h", &basic_get_var);
lua.script("function invoke_userdata() return h(f()) end");
for (auto _ : state)
{
(void) _;
lua["invoke_userdata"]();
}
}
void userdata_variable_write_measure(benchmark::State& state)
{
sol::state lua;
registerBasic(lua);
lua.script("b = c.new()\nfunction write_var() b.var = 24.0 end");
for (auto _ : state)
{
(void) _;
lua["write_var"]();
}
}
void userdata_property_getter_measure(benchmark::State& state)
{
sol::state lua;
registerBasicGetterSetter(lua);
lua.script("b = c.new()\nfunction read_getter() return b.val end");
for (auto _ : state)
{
(void) _;
lua["read_getter"]();
}
}
void userdata_property_setter_measure(benchmark::State& state)
{
sol::state lua;
registerBasicGetterSetter(lua);
lua.script("b = c.new()\nfunction write_setter() b.val = 24.0 end");
for (auto _ : state)
{
(void) _;
lua["write_setter"]();
}
}
void lambda_capture_measure(benchmark::State& state)
{
sol::state lua;
double extra = kMagicValue;
lua.set_function("f", [extra](double v) { return v + extra; });
lua.script("function invoke_lambda() return f(24.0) end");
for (auto _ : state)
{
(void) _;
lua["invoke_lambda"]();
}
}
void shared_ptr_return_measure(benchmark::State& state)
{
sol::state lua;
registerSharedObject(lua);
lua.script("function invoke_shared() return get_shared():get() end");
for (auto _ : state)
{
(void) _;
lua["invoke_shared"]();
}
}
void shared_ptr_pass_measure(benchmark::State& state)
{
sol::state lua;
registerSharedObject(lua);
lua.script("obj = SharedObject()\nfunction invoke_pass_shared() return use_shared(obj) end");
for (auto _ : state)
{
(void) _;
lua["invoke_pass_shared"]();
}
}
void static_member_function_call_measure(benchmark::State& state)
{
sol::state lua;
registerCounter(lua);
lua.script("function invoke_static() return Counter.static_add(10, 32) end");
for (auto _ : state)
{
(void) _;
lua["invoke_static"]();
}
}
void derived_method_call_measure(benchmark::State& state)
{
sol::state lua;
lua.new_usertype<ComplexBaseA>("ComplexBaseA",
"a_func", &ComplexBaseA::a_func,
"a", &ComplexBaseA::a);
lua.new_usertype<ComplexBaseB>("ComplexBaseB",
"b_func", &ComplexBaseB::b_func,
"b", &ComplexBaseB::b);
lua.new_usertype<ComplexAB>("ComplexAB",
sol::constructors<ComplexAB()>(),
sol::base_classes, sol::bases<ComplexBaseA, ComplexBaseB>(),
"ab_func", &ComplexAB::ab_func,
"ab", &ComplexAB::ab);
ComplexAB ab;
lua["obj"] = &ab;
lua.script("function call_derived() return obj:ab_func() end");
for (auto _ : state)
{
(void) _;
lua["call_derived"]();
}
}
void implicit_inheritance_measure(benchmark::State& state)
{
sol::state lua;
lua.new_usertype<ComplexBaseA>("ComplexBaseA",
"a_func", &ComplexBaseA::a_func);
lua.new_usertype<ComplexAB>("ComplexAB",
sol::constructors<ComplexAB()>(),
sol::base_classes, sol::bases<ComplexBaseA>(),
"ab_func", &ComplexAB::ab_func);
lua.set_function("call_a", +[](ComplexBaseA* obj) -> double { return obj->a_func(); });
lua.script("obj = ComplexAB.new()");
lua.script("function test_implicit() return call_a(obj) end");
for (auto _ : state)
{
(void) _;
lua["test_implicit"]();
}
}
} // namespace
BENCHMARK(table_global_string_get_measure)->Name("table_global_string_get_measure");
BENCHMARK(table_global_string_set_measure)->Name("table_global_string_set_measure");
BENCHMARK(table_get_measure)->Name("table_get_measure");
BENCHMARK(table_set_measure)->Name("table_set_measure");
BENCHMARK(table_chained_get_measure)->Name("table_chained_get_measure");
BENCHMARK(table_chained_set_measure)->Name("table_chained_set_measure");
BENCHMARK(c_function_measure)->Name("c_function_measure");
BENCHMARK(c_function_through_lua_in_c_measure)->Name("c_function_through_lua_in_c_measure");
BENCHMARK(lua_function_in_c_measure)->Name("lua_function_in_c_measure");
BENCHMARK(member_function_call_measure)->Name("member_function_call_measure");
BENCHMARK(userdata_variable_access_measure)->Name("userdata_variable_access_measure");
BENCHMARK(userdata_variable_access_large_measure)->Name("userdata_variable_access_large_measure");
BENCHMARK(userdata_variable_access_last_measure)->Name("userdata_variable_access_last_measure");
BENCHMARK(multi_return_lua_measure)->Name("multi_return_lua_measure");
BENCHMARK(multi_return_measure)->Name("multi_return_measure");
BENCHMARK(stateful_function_object_measure)->Name("stateful_function_object_measure");
BENCHMARK(derived_base_measure)->Name("derived_base_measure");
BENCHMARK(return_userdata_measure)->Name("return_userdata_measure");
BENCHMARK(optional_success_measure)->Name("optional_success_measure");
BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure");
BENCHMARK(optional_failure_measure)->Name("optional_failure_measure");
BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure");
BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure");
BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure");
BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure");
BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure");
BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure");
BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure");
BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure");
BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure");
+282
View File
@@ -0,0 +1,282 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
import argparse
import json
from collections import defaultdict
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# ── Label helpers ─────────────────────────────────────────────────────────────
_SUFFIX = "_measure"
def _clean_label(name: str) -> str:
if name.endswith(_SUFFIX):
name = name[: -len(_SUFFIX)]
return name.replace("_", " ")
# ── JSON loading ──────────────────────────────────────────────────────────────
def infer_library_name(path: str) -> str:
return Path(path).stem.replace("benchmark_", "")
def load_google_benchmark_json(path: str, library_name: str) -> dict:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
case_values: dict[str, float] = {}
case_stddev: dict[str, float] = {}
case_errors: dict[str, str] = {}
for entry in data.get("benchmarks", []):
name = entry.get("name", "")
run_type = entry.get("run_type", "")
# Prefer aggregate mean/stddev when available
if run_type == "aggregate":
base_name = entry.get("run_name", name)
agg = entry.get("aggregate_name")
t = entry.get("real_time", entry.get("cpu_time", 0.0))
if agg == "mean":
case_values[base_name] = t
elif agg == "stddev":
case_stddev[base_name] = t
continue
if run_type not in ("iteration", ""):
continue
if entry.get("error_occurred"):
case_errors[name] = entry.get("error_message", "error")
continue
if name not in case_values:
case_values[name] = entry.get("real_time", entry.get("cpu_time", 0.0))
return {"library": library_name, "values": case_values, "stddev": case_stddev, "errors": case_errors}
# ── Merge ─────────────────────────────────────────────────────────────────────
def merge_results(result_sets):
merged: dict[str, dict[str, float]] = defaultdict(dict)
stddev: dict[str, dict[str, float]] = defaultdict(dict)
errors: dict[str, dict[str, str]] = defaultdict(dict)
for result in result_sets:
lib = result["library"]
for case_name, value in result["values"].items():
merged[case_name][lib] = value
for case_name, sd in result.get("stddev", {}).items():
stddev[case_name][lib] = sd
for case_name, error in result["errors"].items():
errors[case_name][lib] = error
return merged, stddev, errors
# ── Plotting ──────────────────────────────────────────────────────────────────
# Dark theme colours
_BG = "#1E1E2E" # figure / axes background
_FG = "#CDD6F4" # text, ticks, labels
_GRID = "#313244" # grid lines
_SPINE = "#45475A" # axis spines
_UNSUP = "#585B70" # "unsupported" text
# Bright palette suited for dark backgrounds
_PALETTE = [
"#89B4FA", # blue
"#FAB387", # peach
"#A6E3A1", # green
"#F38BA8", # red
"#CBA6F7", # mauve
"#94E2D5", # teal
"#F9E2AF", # yellow
"#89DCEB", # sky
]
_LIB_ORDER = ["LuaBridge3Benchmark", "LuaBridgeVanillaBenchmark", "Sol3Benchmark"]
_LIB_ORDER_MAP = {lib: i for i, lib in enumerate(_LIB_ORDER)}
def plot_grouped_bars(merged: dict, stddev: dict, errors: dict, output_file: str, log_scale: bool = False) -> None:
case_names = sorted(merged.keys())
all_libs = {lib for cases in merged.values() for lib in cases}
libraries = sorted(all_libs, key=lambda l: _LIB_ORDER_MAP.get(l, len(_LIB_ORDER)))
if not case_names or not libraries:
raise RuntimeError("No benchmark samples found to plot")
n_cases = len(case_names)
n_libs = len(libraries)
clean_labels = [_clean_label(cn) for cn in case_names]
# ── Layout ────────────────────────────────────────────────────────────────
bar_h = 0.80
group_h = bar_h / n_libs
fig_h = max(10, n_cases * bar_h + 2)
with plt.rc_context({
"text.color": _FG,
"axes.labelcolor": _FG,
"xtick.color": _FG,
"ytick.color": _FG,
}):
fig, ax = plt.subplots(figsize=(14, fig_h))
fig.patch.set_facecolor(_BG)
ax.set_facecolor(_BG)
colors = {lib: _PALETTE[i % len(_PALETTE)] for i, lib in enumerate(libraries)}
y_positions = np.arange(n_cases, dtype=float)
# Max value including error bars — used to size x-axis
x_max_with_err = 1.0
for cn in case_names:
for lib in libraries:
val = merged[cn].get(lib, float("nan"))
if not np.isnan(val):
sd = stddev.get(cn, {}).get(lib, 0.0) or 0.0
x_max_with_err = max(x_max_with_err, val + sd)
for i, library in enumerate(libraries):
values = [merged[cn].get(library, float("nan")) for cn in case_names]
sds = [stddev.get(cn, {}).get(library, float("nan")) for cn in case_names]
bar_y = y_positions + (i - (n_libs - 1) / 2.0) * group_h
xerr_vals = [sd if not np.isnan(sd) else 0.0 for sd in sds]
has_errors = any(sd > 0 for sd in xerr_vals)
ax.barh(
bar_y,
values,
height=group_h * 0.85,
color=colors[library],
label=library,
xerr=xerr_vals if has_errors else None,
error_kw={"ecolor": _FG, "capsize": 3, "elinewidth": 1.2, "capthick": 1.2},
zorder=4,
)
for y, val, sd in zip(bar_y, values, sds):
if np.isnan(val):
ax.text(
0, y, " unsupported",
va="center", ha="left",
fontsize=10, color=_UNSUP, style="italic",
zorder=5,
)
else:
label = f" {val:.1f} ±{sd:.1f} ns" if not np.isnan(sd) and sd > 0 else f" {val:.1f} ns"
ax.text(
val, y, label,
va="center", ha="left",
fontsize=9, color=_FG,
zorder=5,
)
# ── Axes ──────────────────────────────────────────────────────────────
ax.set_yticks(y_positions)
ax.set_yticklabels(clean_labels, fontsize=12)
half_span = (n_libs - 1) / 2.0 * group_h + group_h * 0.425
ax.set_ylim(-half_span, n_cases - 1 + half_span)
ax.invert_yaxis()
if log_scale:
ax.set_xscale("log")
ax.set_xlabel("Time (ns, log scale)", fontsize=13)
else:
ax.set_xlabel("Time (ns)", fontsize=13)
ax.set_xlim(0, x_max_with_err * 1.20)
ax.xaxis.grid(True, color=_GRID, linestyle="--", alpha=1.0, zorder=2)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_edgecolor(_SPINE)
ax.spines["top"].set_visible(True)
ax.spines["right"].set_visible(False)
ax.xaxis.set_tick_params(which="both", top=True, bottom=True, labeltop=True, labelbottom=True)
ax.tick_params(axis="x", which="both", color=_SPINE, labelsize=11)
# ── Legend & title ────────────────────────────────────────────────────
legend_handles = [mpatches.Patch(color=colors[lib], label=lib) for lib in libraries]
ax.legend(
handles=legend_handles,
loc="upper right",
fontsize=11,
framealpha=1.0,
facecolor=_SPINE,
edgecolor=_SPINE,
labelcolor=_FG,
)
ax.set_title(
"Lua Binding Benchmarks — lower is better (ns)",
fontsize=16, pad=10, fontweight="bold", color=_FG,
)
fig.subplots_adjust(left=0.22, right=0.97, top=0.97, bottom=0.03)
plt.savefig(output_file, dpi=150, facecolor=fig.get_facecolor())
plt.close()
# ── Text summary ──────────────────────────────────────────────────────────
txt_file = Path(output_file).with_suffix(".txt")
col_w = max(len(lib) for lib in libraries) + 2
label_w = max(len(lbl) for lbl in clean_labels) + 2
with open(txt_file, "w", encoding="utf-8") as f:
header = f"{'Benchmark':<{label_w}}" + "".join(f"{lib:>{col_w}}" for lib in libraries)
f.write(header + "\n")
f.write("-" * len(header) + "\n")
for cn, lbl in zip(case_names, clean_labels):
row = f"{lbl:<{label_w}}"
for lib in libraries:
val = merged[cn].get(lib)
cell = f"{val:>{col_w - 3}.1f} ns" if val is not None else f"{'n/a':>{col_w}}"
row += cell
f.write(row + "\n")
print(f"Saved: {txt_file}")
# ── Entry point ───────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="Plot comparisons from Google Benchmark JSON files"
)
parser.add_argument(
"--input", nargs="+", required=True,
help="One or more Google Benchmark JSON files"
)
parser.add_argument(
"--output", default="Benchmarks/benchmark_comparison.png",
help="Output PNG file"
)
parser.add_argument(
"--log", action="store_true",
help="Use a logarithmic x-axis"
)
args = parser.parse_args()
result_sets = [
load_google_benchmark_json(path, infer_library_name(path))
for path in args.input
]
merged, stddev, errors = merge_results(result_sets)
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
plot_grouped_bars(merged, stddev, errors, args.output, log_scale=args.log)
print(f"Saved: {args.output}")
if __name__ == "__main__":
main()
+2
View File
@@ -0,0 +1,2 @@
matplotlib
numpy