perf: call mapper as-is in mapped()

This commit is contained in:
littensy 2026-04-01 05:29:55 -07:00
parent 27c7f5a6fd
commit c52f55a55e
2 changed files with 42 additions and 53 deletions

View file

@ -3,55 +3,56 @@ local bench = require("./bench")
local computed = Charm.computed local computed = Charm.computed
local mapped = Charm.mapped local mapped = Charm.mapped
local setActiveSub = Charm.setActiveSub
local signal = Charm.signal local signal = Charm.signal
type Entry = { number } type Key = number
type Value = number
local count = 512 local count = 512
local initialState: { Entry } = table.create(count) local initialState: { [Key]: Value } = table.create(count, 0)
for i = 1, count do local function expensiveOperation()
initialState[i] = table.create(64, i) for _ = 1, 100 do
continue
end
end end
local function mapper(value: Entry, key: number): (Entry, number) local function simpleMapped(
return table.clone(value), key + 1 source: () -> { [Key]: Value },
end mapper: (Value, Key) -> (Value, Key?)
): () -> { [Key]: Value }
local function simpleMapped(source: () -> { Entry }, mapper: (Entry, number) -> (Entry, number)): () -> { Entry }
return computed(function() return computed(function()
local state = source() local state = source()
local nextState = {} local nextState = {}
for index, value in state do for keyIn, valueIn in state do
local currentSub = setActiveSub(nil) local valueOut, keyOut = mapper(valueIn, keyIn)
local success, valueOut, keyOut = pcall(mapper, value, index) nextState[if keyOut == nil then keyIn else keyOut] = valueOut
setActiveSub(currentSub)
if success then
nextState[keyOut] = valueOut
end
end end
return state return nextState
end) end)
end end
local function updatePercentage(percent: number): ({ Entry }) -> { Entry } local function updateRandomly(updateChance: number): ({ [Key]: Value }) -> { [Key]: Value }
return function(state) return function(state)
local nextState = table.clone(state) local nextState = table.clone(state)
for index, value in state do for key in state do
if math.random() < percent then if math.random() < updateChance then
nextState[index] = table.clone(value) nextState[key] += 1
end end
end end
return nextState return nextState
end end
end end
for _, percent in { 0.1, 0.5, 1 } do for _, updateChance in { 0.1, 0.5, 1 } do
bench.run(`mapped {percent}`, function() local function mapper(value: Value, key: Key): (Value, Key?)
expensiveOperation()
return value + 1, key + 1
end
bench.run(`mapped {updateChance}`, function()
local src, setSrc = signal(initialState) local src, setSrc = signal(initialState)
local out = mapped(src, mapper) local out = mapped(src, mapper)
@ -59,14 +60,14 @@ for _, percent in { 0.1, 0.5, 1 } do
out() out()
bench.profileend() bench.profileend()
setSrc(updatePercentage(percent)) setSrc(updateRandomly(updateChance))
bench.profilebegin("update") bench.profilebegin("update")
out() out()
bench.profileend() bench.profileend()
end) end)
bench.run(`control {percent}`, function() bench.run(`control {updateChance}`, function()
local src, setSrc = signal(initialState) local src, setSrc = signal(initialState)
local out = simpleMapped(src, mapper) local out = simpleMapped(src, mapper)
@ -74,7 +75,7 @@ for _, percent in { 0.1, 0.5, 1 } do
out() out()
bench.profileend() bench.profileend()
setSrc(updatePercentage(percent)) setSrc(updateRandomly(updateChance))
bench.profilebegin("update") bench.profilebegin("update")
out() out()

View file

@ -914,22 +914,9 @@ end
@see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper
]] ]]
local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }> local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }>
local map = noYield(mapper)
local nextInput: { [KI]: VI } = {} local nextInput: { [KI]: VI } = {}
local keyMap: { [KI]: KO } = {} local keyMap: { [KI]: KO } = {}
local function mapUntracked(value: VI, key: KI): (VO, KO)
local prevSub = setActiveSub(nil)
local success, valueOut, keyOut = pcall(map, value, key)
activeSub = prevSub
if not success then
error(`mapped() transform function failed: {valueOut}`, 0)
end
return valueOut, if keyOut == nil then key :: any else keyOut
end
return computed(function(lastOutput: { [KO]: VO }?): { [KO]: VO } return computed(function(lastOutput: { [KO]: VO }?): { [KO]: VO }
local nextOutput: { [KO]: VO } = lastOutput or {} local nextOutput: { [KO]: VO } = lastOutput or {}
local prevInput = nextInput local prevInput = nextInput
@ -937,19 +924,19 @@ local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI,
-- For keys that were removed from the input, remove the corresponding -- For keys that were removed from the input, remove the corresponding
-- keys from the output. -- keys from the output.
for prevKeyIn in prevInput do for keyIn in prevInput do
if nextInput[prevKeyIn] ~= nil then if nextInput[keyIn] ~= nil then
continue continue
end end
local prevKeyOut = keyMap[prevKeyIn] local keyOut = keyMap[keyIn]
if prevKeyOut ~= nil then if keyOut ~= nil then
if nextOutput == lastOutput then if nextOutput == lastOutput then
nextOutput = table.clone(nextOutput) nextOutput = table.clone(nextOutput)
end end
nextOutput[prevKeyOut] = nil nextOutput[keyOut] = nil
keyMap[prevKeyIn] = nil keyMap[keyIn] = nil
end end
end end
@ -957,13 +944,14 @@ local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI,
-- the output. If the mapper returns a new key that is different from -- the output. If the mapper returns a new key that is different from
-- the previous call, the old mapped key should be removed from the -- the previous call, the old mapped key should be removed from the
-- output. -- output.
for nextKeyIn, nextValueIn in nextInput do for keyIn, nextValueIn in nextInput do
if nextValueIn == prevInput[nextKeyIn] then if nextValueIn == prevInput[keyIn] then
continue continue
end end
local nextValueOut, nextKeyOut = mapUntracked(nextValueIn, nextKeyIn) local nextValueOut, keyOutOrNil = mapper(nextValueIn, keyIn)
local prevKeyOut = keyMap[nextKeyIn] local nextKeyOut: KO = if keyOutOrNil ~= nil then keyOutOrNil else keyIn :: any
local prevKeyOut = keyMap[keyIn]
if prevKeyOut ~= nil and prevKeyOut ~= nextKeyOut then if prevKeyOut ~= nil and prevKeyOut ~= nextKeyOut then
if nextOutput == lastOutput then if nextOutput == lastOutput then
@ -971,13 +959,13 @@ local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI,
end end
nextOutput[prevKeyOut] = nil nextOutput[prevKeyOut] = nil
nextOutput[nextKeyOut] = nextValueOut nextOutput[nextKeyOut] = nextValueOut
keyMap[nextKeyIn] = nextKeyOut keyMap[keyIn] = nextKeyOut
elseif nextOutput[nextKeyOut] ~= nextValueOut then elseif nextOutput[nextKeyOut] ~= nextValueOut then
if nextOutput == lastOutput then if nextOutput == lastOutput then
nextOutput = table.clone(nextOutput) nextOutput = table.clone(nextOutput)
end end
nextOutput[nextKeyOut] = nextValueOut nextOutput[nextKeyOut] = nextValueOut
keyMap[nextKeyIn] = nextKeyOut keyMap[keyIn] = nextKeyOut
end end
end end