mirror of
https://github.com/littensy/charm.git
synced 2026-10-21 03:36:11 +00:00
perf: optimize mapped, add benches
This commit is contained in:
parent
d56672384a
commit
ada1b80b87
12 changed files with 385 additions and 161 deletions
48
benchmarks/bench.luau
Normal file
48
benchmarks/bench.luau
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
local profiles: { [string]: number } = {}
|
||||
local times: { [string]: number } = {}
|
||||
local stack: { string } = {}
|
||||
|
||||
local function run(name: string, test: () -> ())
|
||||
local start = os.clock()
|
||||
local iter = 0
|
||||
|
||||
table.clear(profiles)
|
||||
table.clear(times)
|
||||
table.clear(stack)
|
||||
|
||||
while os.clock() - start < 1 do
|
||||
iter += 1
|
||||
test()
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - start
|
||||
local results = {}
|
||||
|
||||
table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`)
|
||||
|
||||
for name, elapsed in times do
|
||||
table.insert(results, `{name}: \t{string.format("%.2f", elapsed / iter * 1000 * 1000)} us/iter`)
|
||||
end
|
||||
|
||||
print(table.concat(results, " | "))
|
||||
end
|
||||
|
||||
local function profilebegin(name: string)
|
||||
profiles[name] = os.clock()
|
||||
table.insert(stack, name)
|
||||
end
|
||||
|
||||
local function profileend()
|
||||
local name = table.remove(stack)
|
||||
|
||||
if name then
|
||||
times[name] = (times[name] or 0) + os.clock() - profiles[name]
|
||||
profiles[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
run = run,
|
||||
profilebegin = profilebegin,
|
||||
profileend = profileend,
|
||||
}
|
||||
99
benchmarks/mapped.bench.luau
Normal file
99
benchmarks/mapped.bench.luau
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
--!optimize 2
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local Charm = require(ReplicatedStorage.Packages.Charm)
|
||||
|
||||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local mapped = Charm.mapped
|
||||
local pauseTracking = Charm.signals.pauseTracking
|
||||
local resumeTracking = Charm.signals.resumeTracking
|
||||
|
||||
type Entry = { number }
|
||||
|
||||
local count = 512
|
||||
local initialState: { Entry } = table.create(count)
|
||||
|
||||
for i = 1, count do
|
||||
initialState[i] = table.create(32, i)
|
||||
end
|
||||
|
||||
local function mapper(value: Entry): Entry
|
||||
return table.clone(value)
|
||||
end
|
||||
|
||||
local function simpleMapped(source: () -> { Entry }): () -> { Entry }
|
||||
return computed(function()
|
||||
local state = source()
|
||||
local nextState = {}
|
||||
|
||||
for index, value in state do
|
||||
pauseTracking()
|
||||
local success, valueOut = pcall(mapper, value)
|
||||
resumeTracking()
|
||||
|
||||
if success then
|
||||
nextState[index] = valueOut
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end)
|
||||
end
|
||||
|
||||
local function updatePercentage(percent: number): ({ Entry }) -> { Entry }
|
||||
return function(state)
|
||||
local nextState = table.clone(state)
|
||||
for index, value in state do
|
||||
if math.random() < percent then
|
||||
nextState[index] = table.clone(value)
|
||||
end
|
||||
end
|
||||
return nextState
|
||||
end
|
||||
end
|
||||
|
||||
local function test(
|
||||
percent: number,
|
||||
profiler: any,
|
||||
mapped: (src: () -> { Entry }, ...(Entry) -> Entry) -> () -> { Entry }
|
||||
)
|
||||
local src = atom(initialState)
|
||||
local out = mapped(src, mapper)
|
||||
|
||||
profiler.Begin("init")
|
||||
out()
|
||||
profiler.End()
|
||||
|
||||
profiler.Begin("setup")
|
||||
src(updatePercentage(percent))
|
||||
profiler.End()
|
||||
|
||||
profiler.Begin("update")
|
||||
out()
|
||||
profiler.End()
|
||||
end
|
||||
|
||||
return {
|
||||
Functions = {
|
||||
["mapped 10%"] = function(profiler)
|
||||
test(0.1, profiler, mapped)
|
||||
end,
|
||||
["mapped 50%"] = function(profiler)
|
||||
test(0.5, profiler, mapped)
|
||||
end,
|
||||
["mapped 100%"] = function(profiler)
|
||||
test(1, profiler, mapped)
|
||||
end,
|
||||
["control 10%"] = function(profiler)
|
||||
test(0.1, profiler, simpleMapped)
|
||||
end,
|
||||
["control 50%"] = function(profiler)
|
||||
test(0.5, profiler, simpleMapped)
|
||||
end,
|
||||
["control 100%"] = function(profiler)
|
||||
test(1, profiler, simpleMapped)
|
||||
end,
|
||||
},
|
||||
}
|
||||
80
benchmarks/mapped.luau
Normal file
80
benchmarks/mapped.luau
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
local Charm = require("@pkgs/charm/src/lib")
|
||||
local bench = require("./bench")
|
||||
|
||||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local mapped = Charm.mapped
|
||||
local pauseTracking = Charm.signals.pauseTracking
|
||||
local resumeTracking = Charm.signals.resumeTracking
|
||||
|
||||
type Entry = { number }
|
||||
|
||||
local count = 512
|
||||
local initialState: { Entry } = table.create(count)
|
||||
|
||||
for i = 1, count do
|
||||
initialState[i] = table.create(64, i)
|
||||
end
|
||||
|
||||
local function mapper(value: Entry, key: number): (Entry, number)
|
||||
return table.clone(value), key + 1
|
||||
end
|
||||
|
||||
local function simpleMapped(source: () -> { Entry }, mapper: (Entry, number) -> (Entry, number)): () -> { Entry }
|
||||
return computed(function()
|
||||
local state = source()
|
||||
local nextState = {}
|
||||
|
||||
for index, value in state do
|
||||
pauseTracking()
|
||||
local success, valueOut, keyOut = pcall(mapper, value, index)
|
||||
resumeTracking()
|
||||
|
||||
if success then
|
||||
nextState[keyOut] = valueOut
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end)
|
||||
end
|
||||
|
||||
local function updatePercentage(percent: number): ({ Entry }) -> { Entry }
|
||||
return function(state)
|
||||
local nextState = table.clone(state)
|
||||
for index, value in state do
|
||||
if math.random() < percent then
|
||||
nextState[index] = table.clone(value)
|
||||
end
|
||||
end
|
||||
return nextState
|
||||
end
|
||||
end
|
||||
|
||||
for _, percent in { 0.1, 0.5, 1 } do
|
||||
bench.run(`mapped {percent}`, function()
|
||||
local src = atom(initialState)
|
||||
local out = mapped(src, mapper)
|
||||
|
||||
bench.profilebegin("init")
|
||||
out()
|
||||
bench.profileend()
|
||||
src(updatePercentage(percent))
|
||||
bench.profilebegin("update")
|
||||
out()
|
||||
bench.profileend()
|
||||
end)
|
||||
|
||||
bench.run(`control {percent}`, function()
|
||||
local src = atom(initialState)
|
||||
local out = simpleMapped(src, mapper)
|
||||
|
||||
bench.profilebegin("init")
|
||||
out()
|
||||
bench.profileend()
|
||||
src(updatePercentage(percent))
|
||||
bench.profilebegin("update")
|
||||
out()
|
||||
bench.profileend()
|
||||
end)
|
||||
end
|
||||
54
benchmarks/patch.bench.luau
Normal file
54
benchmarks/patch.bench.luau
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local CharmSync = require(ReplicatedStorage.Packages.CharmSync)
|
||||
|
||||
local patch = CharmSync.patch
|
||||
|
||||
local USERS = 1000
|
||||
|
||||
local function generate()
|
||||
local users = {}
|
||||
|
||||
for user = 1, USERS do
|
||||
users[user] = {
|
||||
coins = math.random(1, 2),
|
||||
gems = math.random(1, 2),
|
||||
}
|
||||
end
|
||||
|
||||
return { users = users }
|
||||
end
|
||||
|
||||
local function update(state)
|
||||
state = table.clone(state)
|
||||
state.users = table.clone(state.users)
|
||||
|
||||
for user = 1, USERS do
|
||||
local rng = math.random()
|
||||
|
||||
if rng < 1 / 3 then
|
||||
continue
|
||||
elseif rng < 2 / 3 then
|
||||
state.users[user] = nil
|
||||
else
|
||||
state.users[user] = {
|
||||
coins = math.random(1, 2),
|
||||
gems = math.random(1, 2),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
return {
|
||||
ParameterGenerator = function()
|
||||
local state = generate()
|
||||
return state, update(state)
|
||||
end,
|
||||
Functions = {
|
||||
diff = function(_, a, b)
|
||||
patch.diff(a, b)
|
||||
end,
|
||||
},
|
||||
}
|
||||
63
benchmarks/propagate.bench.luau
Normal file
63
benchmarks/propagate.bench.luau
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local Charm = require(ReplicatedStorage.Packages.Charm)
|
||||
|
||||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local listen = Charm.listen
|
||||
|
||||
local function propagate(w: number, h: number)
|
||||
local src = atom(1)
|
||||
for _ = 1, w do
|
||||
local last = src
|
||||
for _ = 1, h do
|
||||
local prev = last
|
||||
last = computed(function()
|
||||
return prev() + 1
|
||||
end)
|
||||
end
|
||||
effect(function()
|
||||
last()
|
||||
end)
|
||||
end
|
||||
src(src() + 1)
|
||||
end
|
||||
|
||||
local function propagateListen(w: number, h: number)
|
||||
local src = atom(1)
|
||||
for _ = 1, w do
|
||||
local last = src
|
||||
for _ = 1, h do
|
||||
local prev = last
|
||||
last = computed(function()
|
||||
return prev() + 1
|
||||
end)
|
||||
end
|
||||
listen(last, function() end)
|
||||
end
|
||||
src(src() + 1)
|
||||
end
|
||||
|
||||
return {
|
||||
Functions = {
|
||||
["effect 1*100"] = function()
|
||||
propagate(1, 100)
|
||||
end,
|
||||
["effect 100*1"] = function()
|
||||
propagate(100, 1)
|
||||
end,
|
||||
["effect 10*10"] = function()
|
||||
propagate(10, 10)
|
||||
end,
|
||||
["listen 1*100"] = function()
|
||||
propagateListen(1, 100)
|
||||
end,
|
||||
["listen 100*1"] = function()
|
||||
propagateListen(100, 1)
|
||||
end,
|
||||
["listen 10*10"] = function()
|
||||
propagateListen(10, 10)
|
||||
end,
|
||||
},
|
||||
}
|
||||
27
benchmarks/propagate.luau
Normal file
27
benchmarks/propagate.luau
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
local Charm = require("@pkgs/charm/src/lib")
|
||||
local bench = require("./bench")
|
||||
|
||||
local atom = Charm.atom
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
|
||||
for _, w in { 1, 10, 100 } do
|
||||
for _, h in { 1, 10, 100 } do
|
||||
bench.run(`propagate {w}*{h}`, function()
|
||||
local src = atom(1)
|
||||
for _ = 1, w do
|
||||
local last = src
|
||||
for _ = 1, h do
|
||||
local prev = last
|
||||
last = computed(function()
|
||||
return prev() + 1
|
||||
end)
|
||||
end
|
||||
effect(function()
|
||||
last()
|
||||
end)
|
||||
end
|
||||
src(src() + 1)
|
||||
end)
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue