Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions benchmarks/closures.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Run with: mix run benchmarks/closures.exs
#
# Benchmarks closures and upvalue capture.
# Creates 100 counter closures via a factory function, each starting at a different value,
# then calls each counter 10 times, accumulating the sum.
#
# Compares:
# - This Lua implementation (eval with string, eval with pre-compiled chunk)
# - Luerl (Erlang-based Lua 5.3 implementation)
# - C Lua 5.4 via luaport (port-based; results include IPC overhead)
#
# NOTE: luaport requires C Lua development headers. On macOS with Homebrew:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport
# Then run:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/closures.exs

Application.ensure_all_started(:luerl)

closure_def = """
function make_counter(start)
local count = start
return function()
count = count + 1
return count
end
end

function run_closures(n)
local counters = {}
for i = 1, n do
counters[i] = make_counter(i)
end
local sum = 0
for j = 1, n do
local counter = counters[j]
for k = 1, 10 do
sum = sum + counter()
end
end
return sum
end
"""

call_closures = "return run_closures(100)"

# --- This Lua implementation ---
lua = Lua.new()
{_, lua} = Lua.eval!(lua, closure_def)
{closure_chunk, _} = Lua.load_chunk!(lua, call_closures)

# --- Luerl ---
luerl_state = :luerl.init()
{:ok, _, luerl_state} = :luerl.do(closure_def, luerl_state)

# --- C Lua via luaport (optional) ---
{c_lua_benchmarks, c_lua_cleanup} =
case Application.ensure_all_started(:luaport) do
{:ok, _} ->
scripts_dir = Path.join(__DIR__, "scripts")
{:ok, port_pid, _} = :luaport.spawn(:closure_bench, to_charlist(scripts_dir))
:luaport.load(port_pid, closure_def)

benchmarks = %{
"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_closures, [100]) end
}

{benchmarks, fn -> :luaport.despawn(:closure_bench) end}

{:error, reason} ->
IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks")
{%{}, fn -> :ok end}
end

Benchee.run(
Map.merge(
%{
"lua (eval)" => fn -> Lua.eval!(lua, call_closures) end,
"lua (chunk)" => fn -> Lua.eval!(lua, closure_chunk) end,
"luerl" => fn -> :luerl.do(call_closures, luerl_state) end
},
c_lua_benchmarks
),
time: 10,
warmup: 2,
memory_time: 1
)

c_lua_cleanup.()
66 changes: 66 additions & 0 deletions benchmarks/fibonacci.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Run with: mix run benchmarks/fibonacci.exs
#
# Benchmarks recursive Fibonacci (fib(30)) across:
# - This Lua implementation (eval with string, eval with pre-compiled chunk)
# - Luerl (Erlang-based Lua 5.3 implementation)
# - C Lua 5.4 via luaport (port-based; results include IPC overhead)
#
# NOTE: luaport requires C Lua development headers. On macOS with Homebrew:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport
# Then run:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/fibonacci.exs

Application.ensure_all_started(:luerl)

fib_def = """
function fib(n)
if n < 2 then return n end
return fib(n-1) + fib(n-2)
end
"""

call_fib = "return fib(30)"

# --- This Lua implementation ---
lua = Lua.new()
{_, lua} = Lua.eval!(lua, fib_def)
{fib_chunk, _} = Lua.load_chunk!(lua, call_fib)

# --- Luerl ---
luerl_state = :luerl.init()
{:ok, _, luerl_state} = :luerl.do(fib_def, luerl_state)

# --- C Lua via luaport (optional) ---
{c_lua_benchmarks, c_lua_cleanup} =
case Application.ensure_all_started(:luaport) do
{:ok, _} ->
scripts_dir = Path.join(__DIR__, "scripts")
{:ok, port_pid, _} = :luaport.spawn(:fib_bench, to_charlist(scripts_dir))
:luaport.load(port_pid, fib_def)

benchmarks = %{
"C Lua (luaport)" => fn -> :luaport.call(port_pid, :fib, [30]) end
}

{benchmarks, fn -> :luaport.despawn(:fib_bench) end}

{:error, reason} ->
IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks")
{%{}, fn -> :ok end}
end

Benchee.run(
Map.merge(
%{
"lua (eval)" => fn -> Lua.eval!(lua, call_fib) end,
"lua (chunk)" => fn -> Lua.eval!(lua, fib_chunk) end,
"luerl" => fn -> :luerl.do(call_fib, luerl_state) end
},
c_lua_benchmarks
),
time: 10,
warmup: 2,
memory_time: 1
)

c_lua_cleanup.()
99 changes: 99 additions & 0 deletions benchmarks/oop.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Run with: mix run benchmarks/oop.exs
#
# Benchmarks object-oriented patterns using Lua tables and metatables.
# Uses assignment-style method definitions (e.g. Animal.speak = function(self) ... end)
# which are compatible with this Lua implementation's current feature set.
# Creates 50 Animal instances per iteration and calls a method on each.
#
# Patterns tested:
# - Table creation and field assignment
# - setmetatable / __index prototype chain lookup
# - Closure creation per object (factory pattern variant)
#
# Compares:
# - This Lua implementation (eval with string, eval with pre-compiled chunk)
# - Luerl (Erlang-based Lua 5.3 implementation)
# - C Lua 5.4 via luaport (port-based; results include IPC overhead)
#
# NOTE: luaport requires C Lua development headers. On macOS with Homebrew:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport
# Then run:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/oop.exs

Application.ensure_all_started(:luerl)

oop_def = """
Animal = {}
Animal.__index = Animal

Animal.new = function(name, sound)
local obj = {}
obj.name = name
obj.sound = sound
setmetatable(obj, Animal)
return obj
end

Animal.speak = function(self)
return self.name .. " says " .. self.sound
end

Animal.getName = function(self)
return self.name
end

function run_oop(n)
local result = ""
for i = 1, n do
local a = Animal.new("Animal" .. tostring(i), "sound" .. tostring(i))
result = Animal.speak(a)
end
return result
end
"""

call_oop = "return run_oop(50)"

# --- This Lua implementation ---
lua = Lua.new()
{_, lua} = Lua.eval!(lua, oop_def)
{oop_chunk, _} = Lua.load_chunk!(lua, call_oop)

# --- Luerl ---
luerl_state = :luerl.init()
{:ok, _, luerl_state} = :luerl.do(oop_def, luerl_state)

# --- C Lua via luaport (optional) ---
{c_lua_benchmarks, c_lua_cleanup} =
case Application.ensure_all_started(:luaport) do
{:ok, _} ->
scripts_dir = Path.join(__DIR__, "scripts")
{:ok, port_pid, _} = :luaport.spawn(:oop_bench, to_charlist(scripts_dir))
:luaport.load(port_pid, oop_def)

benchmarks = %{
"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_oop, [50]) end
}

{benchmarks, fn -> :luaport.despawn(:oop_bench) end}

{:error, reason} ->
IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks")
{%{}, fn -> :ok end}
end

Benchee.run(
Map.merge(
%{
"lua (eval)" => fn -> Lua.eval!(lua, call_oop) end,
"lua (chunk)" => fn -> Lua.eval!(lua, oop_chunk) end,
"luerl" => fn -> :luerl.do(call_oop, luerl_state) end
},
c_lua_benchmarks
),
time: 10,
warmup: 2,
memory_time: 1
)

c_lua_cleanup.()
2 changes: 2 additions & 0 deletions benchmarks/scripts/main.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Main script loaded by luaport on startup.
-- Benchmark-specific functions are loaded at runtime via :luaport.load/2.
100 changes: 100 additions & 0 deletions benchmarks/string_ops.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Run with: mix run benchmarks/string_ops.exs
#
# Benchmarks string operations:
# - table.concat: builds 100 string parts in a table then joins them
# - string.format: formats 100 strings with integer and float values
#
# Compares:
# - This Lua implementation (eval with string, eval with pre-compiled chunk)
# - Luerl (Erlang-based Lua 5.3 implementation)
# - C Lua 5.4 via luaport (port-based; results include IPC overhead)
#
# NOTE: luaport requires C Lua development headers. On macOS with Homebrew:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix deps.compile luaport
# Then run:
# PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig mix run benchmarks/string_ops.exs

Application.ensure_all_started(:luerl)

string_def = """
function run_concat(n)
local parts = {}
for i = 1, n do
parts[i] = tostring(i)
end
return table.concat(parts, ", ")
end

function run_format(n)
local parts = {}
for i = 1, n do
parts[i] = string.format("item_%d=%f", i, i * 1.5)
end
return table.concat(parts, "\\n")
end
"""

call_concat = "return run_concat(100)"
call_format = "return run_format(100)"

# --- This Lua implementation ---
lua = Lua.new()
{_, lua} = Lua.eval!(lua, string_def)
{concat_chunk, _} = Lua.load_chunk!(lua, call_concat)
{format_chunk, _} = Lua.load_chunk!(lua, call_format)

# --- Luerl ---
luerl_state = :luerl.init()
{:ok, _, luerl_state} = :luerl.do(string_def, luerl_state)

# --- C Lua via luaport (optional) ---
{c_lua_concat, c_lua_format, c_lua_cleanup} =
case Application.ensure_all_started(:luaport) do
{:ok, _} ->
scripts_dir = Path.join(__DIR__, "scripts")
{:ok, port_pid, _} = :luaport.spawn(:string_bench, to_charlist(scripts_dir))
:luaport.load(port_pid, string_def)

concat = %{"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_concat, [100]) end}
format = %{"C Lua (luaport)" => fn -> :luaport.call(port_pid, :run_format, [100]) end}

{concat, format, fn -> :luaport.despawn(:string_bench) end}

{:error, reason} ->
IO.puts("luaport not available (#{inspect(reason)}) — skipping C Lua benchmarks")
{%{}, %{}, fn -> :ok end}
end

IO.puts("\n=== String Concatenation via table.concat (n=100) ===\n")

Benchee.run(
Map.merge(
%{
"lua (eval)" => fn -> Lua.eval!(lua, call_concat) end,
"lua (chunk)" => fn -> Lua.eval!(lua, concat_chunk) end,
"luerl" => fn -> :luerl.do(call_concat, luerl_state) end
},
c_lua_concat
),
time: 10,
warmup: 2,
memory_time: 1
)

IO.puts("\n=== String Formatting via string.format (n=100) ===\n")

Benchee.run(
Map.merge(
%{
"lua (eval)" => fn -> Lua.eval!(lua, call_format) end,
"lua (chunk)" => fn -> Lua.eval!(lua, format_chunk) end,
"luerl" => fn -> :luerl.do(call_format, luerl_state) end
},
c_lua_format
),
time: 10,
warmup: 2,
memory_time: 1
)

c_lua_cleanup.()
Loading
Loading