From bbe634f4336d96de8aebb8d2e1799e12b9cea1c7 Mon Sep 17 00:00:00 2001 From: aaron <83140718+centau@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:53:47 +0100 Subject: [PATCH] --- src/graph.luau | 23 +-- test/benchmark.luau | 474 +++++++++++++++++++++----------------------- 2 files changed, 231 insertions(+), 266 deletions(-) diff --git a/src/graph.luau b/src/graph.luau index a70fe57..33ed011 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -114,6 +114,8 @@ local function destroy(node: Node) while node[1] do destroy(node[1]) end end +local update_queue = {} :: { Node } + local function evaluate_node(node: Node) local cur_value = node.cache @@ -136,6 +138,7 @@ local function evaluate_node(node: Node) close_scope() if not ok then + table.clear(update_queue) throw(`side-effect error from source update\n{new_value}`) end @@ -144,19 +147,11 @@ local function evaluate_node(node: Node) return cur_value ~= new_value -- node has changed value end -local update_queue = {} :: { Node } - -local function update(node: StartNode) +local function update_from(node: StartNode, n0: number) if not node[1] then return end - local n0 = #update_queue - local first_update = n0 == 0 local n = n0 - if first_update then - table.clear(update_queue) - end - -- unparent all children and queue for eval do local child = node[1] @@ -176,15 +171,17 @@ local function update(node: StartNode) if not child.effect then continue end if evaluate_node(child) then - update(child) + update_from(child, n) end - end - if first_update then - table.clear(update_queue) + update_queue[i] = false :: any -- false instead of nil to avoid sparse end end +local function update(node: StartNode) + update_from(node, 0) +end + local function track(node: StartNode) local scope = get_scope() if scope and scope.effect then -- do not track nodes with no effect diff --git a/test/benchmark.luau b/test/benchmark.luau index bf7085a..f858f5f 100644 --- a/test/benchmark.luau +++ b/test/benchmark.luau @@ -1,7 +1,7 @@ -local BENCH, START = require("test/testkit").benchmark() +local testkit = require("test/testkit") +local BENCH, START = testkit.benchmark() local vide = require "src/init" -local root = vide.root local source = vide.source local derive = vide.derive local indexes = vide.indexes @@ -9,11 +9,23 @@ local values = vide.values local cleanup = vide.cleanup local create = vide.create +local function TITLE(name: string) + print() + print(testkit.color.white(name)) +end + local N = 2^18 -- 262144 --- todo: wide and deep graph benchmarks +local function WRAP_BENCH(name: string, fn: () -> ()) + local _, destroy = vide.root(function() + BENCH(name, fn) + end) + destroy() +end -BENCH("create source", function() +TITLE "sources" + +WRAP_BENCH("create source", function() local cache = table.create(N) for i = 1, START(N) do @@ -21,7 +33,7 @@ BENCH("create source", function() end end) -BENCH("get value", function() +WRAP_BENCH("get value", function() local src = source(1) for i = 1, START(N) do @@ -29,7 +41,7 @@ BENCH("get value", function() end end) -BENCH("set value", function() +WRAP_BENCH("set value", function() local src = source(1) for i = 1, START(N) do @@ -37,148 +49,129 @@ BENCH("set value", function() end end) -BENCH("derive 1 source", function() +WRAP_BENCH("derive 1 source", function() local cache = table.create(N) local src = source(1) - root(function() - for i = 1, START(N) do - cache[i] = derive(function() - return src() - end) - end - return nil - end) + for i = 1, START(N) do + cache[i] = derive(function() + return src() + end) + end end) -BENCH("derive 4 sources", function() +WRAP_BENCH("derive 4 sources", function() local cache = table.create(N) local src = vide.source(1) local src2 = vide.source(2) local src3 = vide.source(3) local src4 = vide.source(4) - vide.root(function() - for i = 1, START(N) do - cache[i] = derive(function() - return src() + src2() + src3() + src4() - end) - end - - return nil - end) -end) - -BENCH("update 1->1 graph", function() - local src = source(1) - - root(function() - local _derived = derive(function() return src() end) - - for i = 1, START(N) do - src(i) - end - - return nil - end) -end) - -BENCH("update 1->1 graph with cleanup", function() - local src = source(1) - - root(function() - derive(function() - cleanup(function() end) - return src() + for i = 1, START(N) do + cache[i] = derive(function() + return src() + src2() + src3() + src4() end) - - for i = 1, START(N) do - src(i) - end - - return nil - end) + end end) -BENCH("update 1->1000 graph", function() - local src = source(-1) +TITLE "graphs" - root(function() - for i = 1, 1000 do - derive(function() return src() end) - end +WRAP_BENCH("update 1->1 graph", function() + local src = source(1) - src(0) + local _derived = derive(function() return src() end) - for i = 1, START(10) do - src(i) - end - end) + for i = 1, START(N) do + src(i) + end end) -BENCH("update 1->1->1->1...1000 graph", function() +WRAP_BENCH("update 1->1 graph with cleanup", function() + local src = source(1) + + derive(function() + cleanup(function() end) + return src() + end) + + for i = 1, START(N) do + src(i) + end +end) + +WRAP_BENCH("update 1->1000 graph", function() local src = source(-1) - root(function() - local last = src - for i = 1, 1000 do - local l = last - last = derive(function() return l() end) - end + for i = 1, 1000 do + derive(function() return src() end) + end - src(0) + src(0) - for i = 1, START(10) do - src(i) - end - end) + for i = 1, START(10) do + src(i) + end +end) + +WRAP_BENCH("update 1->1->1->1...1000 graph", function() + local src = source(-1) + + local last = src + for i = 1, 1000 do + local l = last + last = derive(function() return l() end) + end + + src(0) + + for i = 1, START(10) do + src(i) + end end) -- todo: repeat with batching -BENCH("update 1000->1 graph", function() +WRAP_BENCH("update 1000->1 graph", function() local srcs = {} for i = 1, 1000 do srcs[i] = source(0) end - root(function() - derive(function() - for i = 1, 1000 do - srcs[i]() - end - return false - end) - - for i = 1, START(1) do - for idx = 1, 1000 do - srcs[idx](i) - end + derive(function() + for i = 1, 1000 do + srcs[i]() end + return false end) + + for i = 1, START(1) do + for idx = 1, 1000 do + srcs[idx](i) + end + end end) -- todo: optimize, repeat with batching -BENCH("update 1000x 1->1 common extern. graph", function() +WRAP_BENCH("update 1000x 1->1 common extern. graph", function() local ext = source(-1) - root(function() - local srcs = {} - for i = 1, 1000 do - srcs[i] = source(0) - derive(function() return srcs[i]() + ext() end) - end + local srcs = {} + for i = 1, 1000 do + srcs[i] = source(0) + derive(function() return srcs[i]() + ext() end) + end - ext(0) + ext(0) - for i = 1, START(10) do - for idx = 1, 1000 do - srcs[idx](i) - end + for i = 1, START(10) do + for idx = 1, 1000 do + srcs[idx](i) end - end) + end end) -BENCH("apply 0 properties", function() +TITLE "property apply" + +WRAP_BENCH("apply 0 properties", function() local apply = require "src/apply" local instance = create("Frame") {} @@ -187,7 +180,7 @@ BENCH("apply 0 properties", function() end end) -BENCH("apply 8 properties", function() +WRAP_BENCH("apply 8 properties", function() local apply = require "src/apply" local instance = create("Frame") {} @@ -205,45 +198,43 @@ BENCH("apply 8 properties", function() end end) -BENCH("bind property", function() +WRAP_BENCH("bind property", function() local apply = require "src/apply" local instance = create("Frame") {} local src = source(1) - root(function() - for i = 1, START(N) do - apply(instance, { - Text = src - }) - end - - return nil - end) -end) - -BENCH("update binding", function() - local apply = require "src/apply" - - local instance = create("Frame") {} - local src = source(1) - - root(function() + for i = 1, START(N) do apply(instance, { Text = src }) + end - for i = 1, START(N) do - src(i) - end - - return nil - end) + return nil end) +WRAP_BENCH("update binding", function() + local apply = require "src/apply" + + local instance = create("Frame") {} + local src = source(1) + + apply(instance, { + Text = src + }) + + for i = 1, START(N) do + src(i) + end + + return nil +end) + +TITLE "indexes()" + N /= 1024 -BENCH("indexes() all new", function() +WRAP_BENCH("indexes() all new", function() local data = {} for i = 1, N do @@ -252,18 +243,16 @@ BENCH("indexes() all new", function() local src = source(data) - root(function() - START(N) + START(N) - local _list = indexes(src, function(v, i) - return {} - end) - - return nil + local _list = indexes(src, function(v, i) + return {} end) + + return nil end) -BENCH("indexes() no change", function() +WRAP_BENCH("indexes() no change", function() local data = {} for i = 1, N do @@ -272,20 +261,18 @@ BENCH("indexes() no change", function() local src = source(data) - root(function() - local _list = indexes(src, function(v, i) - return {} - end) - - START(N) - - src(data) - - return nil + local _list = indexes(src, function(v, i) + return {} end) + + START(N) + + src(data) + + return nil end) -BENCH("indexes() all change", function() +WRAP_BENCH("indexes() all change", function() local data = {} for i = 1, N do @@ -294,27 +281,22 @@ BENCH("indexes() all change", function() local src = source(data) - - root(function() - local _list = indexes(src, function(v, i) - return {} - end) - - --src(src()) -- fill double buffer - - for i, v in data do - data[i] = v + 1 - end - - START(N) - - src(data) - - return nil + local _list = indexes(src, function(v, i) + return {} end) + + --src(src()) -- fill double buffer + + for i, v in data do + data[i] = v + 1 + end + + START(N) + + src(data) end) -BENCH("indexes() all remove", function() +WRAP_BENCH("indexes() all remove", function() local data = {} for i = 1, N do @@ -323,22 +305,22 @@ BENCH("indexes() all remove", function() local src = source(data) - root(function() - local _list = indexes(src, function(v, i) - return {} - end) - - table.clear(data) - - START(N) - - src(data) - - return nil + local _list = indexes(src, function(v, i) + return {} end) + + table.clear(data) + + START(N) + + src(data) + + return nil end) -BENCH("values() all new", function() +TITLE "values()" + +WRAP_BENCH("values() all new", function() local data = {} for i = 1, N do @@ -347,18 +329,16 @@ BENCH("values() all new", function() local src = source(data) - root(function() - START(N) - - local _list = values(src, function(v, i) - return {} - end) - - return nil + START(N) + + local _list = values(src, function(v, i) + return {} end) + + return nil end) -BENCH("values() no change", function() +WRAP_BENCH("values() no change", function() local data = {} for i = 1, N do @@ -367,22 +347,18 @@ BENCH("values() no change", function() local src = source(data) - root(function() - local _list = values(src, function(v, i) - return {} - end) - - src(src()) -- fill double buffer - - START(N) - - src(data) - - return nil + local _list = values(src, function(v, i) + return {} end) + + src(src()) -- fill double buffer + + START(N) + + src(data) end) -BENCH("values() all change", function() +WRAP_BENCH("values() all change", function() local data = {} for i = 1, N do @@ -391,27 +367,23 @@ BENCH("values() all change", function() local src = source(data) - root(function() - local _list = values(src, function(v, i) - return {} - end) - - src(src()) -- fill double buffer - - for i = 1, N do - local r = math.random(1, #data) - data[i], data[r] = data[r], data[i] - end - - START(N) - - src(data) - - return nil + local _list = values(src, function(v, i) + return {} end) + + src(src()) -- fill double buffer + + for i = 1, N do + local r = math.random(1, #data) + data[i], data[r] = data[r], data[i] + end + + START(N) + + src(data) end) -BENCH("values() all remove", function() +WRAP_BENCH("values() all remove", function() local data = {} for i = 1, N do @@ -420,50 +392,46 @@ BENCH("values() all remove", function() local src = source(data) - root(function() - local _list = values(src, function(v, i) - return {} - end) - - table.clear(data) - - START(N) - - src(data) - - return nil + local _list = values(src, function(v, i) + return {} end) + + table.clear(data) + + START(N) + + src(data) end) N *= 1024 -BENCH("register new cleanup", function() +TITLE "cleanup" + +WRAP_BENCH("register new cleanup", function() local cleanup = cleanup - root(function() - local cleaner = function() end + local cleaner = function() end - local callers = {} + local callers = {} - for i = 1, N do - callers[i] = function(fn, v) - fn(v) - return i -- return unique upvalue to ensure unique closure - end + for i = 1, N do + callers[i] = function(fn, v) + fn(v) + return i -- return unique upvalue to ensure unique closure end + end - for i = 1, START(N) do - callers[i](cleanup, cleaner) - end - - return nil - end) + for i = 1, START(N) do + callers[i](cleanup, cleaner) + end end) +TITLE "aggregate" + do -- the purpose of the two following benchmarks is to measure the overhead of -- aggregate construction - BENCH("set explicit mock vector2", function() + WRAP_BENCH("set explicit mock vector2", function() local apply = require "src/apply" local Vector2 = require "test/mock".Vector2 @@ -478,7 +446,7 @@ do end end) - BENCH("set aggregate mock vector2", function() + WRAP_BENCH("set aggregate mock vector2", function() local apply = require "src/apply" local Vector2 = require "test/mock".Vector2 @@ -497,7 +465,7 @@ end -- innacurate due to no Vector3 in vanilla Luau -- mock vector is 200x slower than native vector --- BENCH("spring update", function() +-- WRAP_BENCH("spring update", function() -- local root, source, spring = vide.root, vide.source, vide.spring -- local src = source(0) @@ -517,7 +485,7 @@ end -- N /= 1024 --- BENCH("spring step", function() +-- WRAP_BENCH("spring step", function() -- local root, source, spring = vide.root, vide.source, vide.spring -- local src = source(0)