Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
113 changes: 113 additions & 0 deletions lua/benchmarks/value-access.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
-- Global access: 0.122ms
-- Global CFunc Access: 6.35ms
-- Upvalue access: 0.078ms
-- Upvalued CFunc Access: 6.23ms
-- Local access: 0.061ms
-- Local CFunc Access: 6.29ms

-- Conclusion: It is faster to access values in any way through lua instead of using CFuncs.

ModuleName = "Value access"
BenchmarkData = {
BrainGlobalAccess = "Global access",
BrainUpvalueAccess = "Upvalue access",
BrainLocalAccess = "Local access",
BrainGlobalCFuncAccess = "Global CFunc Access",
BrainUpvaluedCFuncAccess = "Upvalued CFunc Access",
BrainLocalCFuncAccess = "Local CFunc Access",
}

function BrainGlobalAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse

local a

local start = timer()

for _ = 1, loop do
a = ArmyBrains[1]
end

local final = timer()
return final - start
end

local ArmyBrains = ArmyBrains
function BrainUpvalueAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse

local a

local start = timer()

for _ = 1, loop do
a = ArmyBrains[1]
end

local final = timer()
return final - start
end

function BrainLocalAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse
local armyBrains = ArmyBrains

local a

local start = timer()

for _ = 1, loop do
a = armyBrains[1]
end

local final = timer()
return final - start
end

function BrainGlobalCFuncAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse

local a

local start = timer()

for _ = 1, loop do
a = GetArmyBrain(1)
end

local final = timer()
return final - start
end

local GetArmyBrain = GetArmyBrain
function BrainUpvaluedCFuncAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse

local a

local start = timer()

for _ = 1, loop do
a = GetArmyBrain(1)
end

local final = timer()
return final - start
end

function BrainLocalCFuncAccess(loop)
local timer = GetSystemTimeSecondsOnlyForProfileUse

local getArmyBrain = GetArmyBrain

local a

local start = timer()

for _ = 1, loop do
a = getArmyBrain(1)
end

local final = timer()
return final - start
end
10 changes: 3 additions & 7 deletions lua/sim/Profiler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
-- - https://www.lua.org/pil/23.1.html


local SerializableDeepCopy = import("/lua/utilities.lua").SerializableDeepCopy

local CollapseDebugInfo = import("/lua/shared/debugfunction.lua").CollapseDebugInfo
local CreateEmptyProfilerTable = import("/lua/shared/profiler.lua").CreateEmptyProfilerTable
local GetDebugFunctionInfo = import("/lua/shared/debugfunction.lua").GetDebugFunctionInfo
Expand Down Expand Up @@ -399,13 +401,7 @@ BenchmarkModuleLoader = Class() {

-- can't serialize functions
info.info.func = nil
for k, v in info.upvalues do
if type(v) == "function" then
info.upvalues[k] = "<func>"
elseif type(v) == "cfunction" then
info.upvalues[k] = "<cfunc>"
end
end
info.upvalues = SerializableDeepCopy(info.upvalues)

return {
name = funName,
Expand Down
52 changes: 52 additions & 0 deletions lua/utilities.lua
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,55 @@ function RotateVectorXYZByQuat(vX, vY, vZ, q)
vY * qW + vZ * qX + vW * qY - vX * qZ,
vZ * qW - vY * qX + vX * qY + vW * qZ
end

--- These are the types that the engine can send across the ui-sim boundary.
--- Tables have to be handled for loops and unserializable keys/values.
SERIALIZABLE_TYPES = {
number = true,
string = true,
boolean = true,
["nil"] = true,
}
local serializableTypes = SERIALIZABLE_TYPES

--- Returns a deep copy with unserializable values converted using `tostring`
--- and cyclic references converted to strings referencing the `_seenTableId` key.
--- The result can be sent across the ui-sim boundary.
---@generic T
---@param t T
---@return T
function SerializableDeepCopy(t)
local st = serializableTypes
local type_t = type(t)
if type_t ~= 'table' then
return st[type_t] and t or tostring(t)
end

local backrefs = {}
local function CreateSerializableAny(_t)
local type_t = type(_t)
if type_t ~= 'table' then
return st[type_t] and _t or tostring(_t)
end

local b = backrefs[_t]
if b then
-- store table id since table `tostring` uses memory location which won't be the same between Sim and UI
if not b._seenTableId then
b._seenTableId = tostring(_t)
end
-- format makes it easy to find in repr output
return '_seenTableId = "' .. b._seenTableId .. '"'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the table ID never changes, why dirty the table with an unoriginal field to store the seen table ID? We could use tostring every time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table ID should change after being copied across the sim-ui boundary, so the table tostring won't give useful information.
An alternative to not dirty the table would be to return an extra table documenting all the references that were converted to strings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you already are using tostring; as you've written it right now, if a table's _seenTableId field exists, it is always filled with tostring applied to that table. You gave a fine reason to use tostring in your previous reply, so I'm not sure why you're saying it doesn't give any useful information here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if a table's _seenTableId field exists, it is always filled with tostring applied to that table

This is not true because the table is presumedly copied across sim-ui, where the field will be filled with tostring applied to the old table before the copying. When you read the copied table you need the old tostring ID to rebuild cyclic references.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see - I thought _seenTableId was only being used for serialization (the return value on line 618). But you also want to leave it as a marker as part of the data in case you want to deserialize it (which we haven't set up) rather than only using the data we pass for textual purposes.

end

local r = {}
backrefs[_t] = r
for k, v in _t do
r[CreateSerializableAny(k)] = CreateSerializableAny(v)
end

return r
end

return CreateSerializableAny(t)
end