Hey, welcome to my post. Its in regards to creating very large grid data representation.
When a player initally connects to the game, the server has to boot up. I have this script on server bootup that basically creates Grid[x][y]
Can someone provide some performance tips to this ?
for x=1,gridSizeX, 1 do
Grid[x] = {}
for y=1,gridSizeY, 1 do
local WorldPoint = worldBottomLeft + Vector3.new(1,0,0) * (x * nodeDiameter + nodeRadius) + Vector3.new(0,0,1) * (y * nodeDiameter + nodeRadius)
local min = WorldPoint - (((nodeDiameter + nodeRadius)) * part.Size)
local max = WorldPoint + (((nodeDiameter + nodeRadius)) * part.Size)
local currentRegion = Region3.new(min, max)
local notWalkableFound = false
for _,Part in pairs(game.Workspace:FindPartsInRegion3(currentRegion,nil,5)) do
if(Part.Parent.Name == "Unwalkable" == true) then
notWalkableFound = true
break;
end
end
local walkable = true
if(notWalkableFound == true) then
walkable = false
end
local penalty = 1
if(walkable == false) then
penalty = penalty + obstaclePromityPenalty
end
print("GridStorage["..x.."]["..y.."] = NodeClass.new(", walkable, ", Vector3.new(", WorldPoint.X, ", 0, ", WorldPoint.Z, "), ",x,", ",y,", ",penalty,")")
--Grid[x][y] = NodeClass.new(walkable, Vector3.new(WorldPoint.X, 0, WorldPoint.Z), x, y, penalty)--]]
end
wait()
end
This takes pretty long to load up so I was wondering if someone could help optimize the code here a bit. Enough to make a dent in the time it takes to load all of them
Avoid doing the same calculations repeatedly – I can see lots of things related to nodes that could be calculated once then stored in a variable, or calculated for every x value (not for every y in every x).
Example:
local nodeDimensions = nodeDiameter + nodeRadius -- Just made up a random name for this one
local nodeSize = nodeDimensions * part.Size
for x = 1, gridSizeX do
local nodeX = x * nodeDimensions
for y = 1, gridSize Y do
-- code here
end
end
Note you can use workspace instead of game.Workspace (probably won’t have a noticeable impact on performance, but it helps for writing).
The not walkable detection code will probably be a big slowdown too. If it’s not instantly needed, consider making that all run after creating the grid.
Also you have a lot of redundant code that will be impacting performance (even if its minor). For example: Part.Parent.Name == "Unwalkable" == true in which that last check for == true is pointless. for x = 1, gridSize, 1 do that last 1 is redundant because the for loop increments by 1 by default.
Also: not sure exactly how you know the performance issues are from your code, as note that Roblox servers can take a while to start up anyway.
I’m fairly certain this is going to have a huge impact. Constantly creating new string objects, as well as printing them to the console, takes a long time. I tried doing a benchmark of three different versions of the same terrain generation function:
No string objects and no print statements
String objects but no print statements
String objects printed to the console
I should probably have two additional versions, where I only create and print strings for every row, instead for every row and column. But oh well.
I don’t really know anything about how to do proper benchmarks, but I feel like my results were pretty significant even if there’s a lot of noise and/or systematic error.
Here are my benchmark results
Terrain with no string and no print:
Iterations: 100
Time: 12.670662
Terrain with string and no print:
Iterations: 100
Time: 12.525537
Terrain with string and print:
Iterations: 100
Time: 66.672688
Here's my benchmark code
local noise = math.noise
local freq = 8
local power = 1
local size = 64
local seed = os.time()
local p = Instance.new(“Part”)
p.Size = Vector3.new(1, 1, 1)
p.Anchored = true
function generateTerrain1()
local terrain = Instance.new(“Model”)
for x = 1, size do
for y = 1, size do
local v = noise((freq*x)/(size), (freq*y)/(size), seed) * power
local p = p:Clone()
p.CFrame = CFrame.new(x, v, y)
p.Parent = terrain
end
end
terrain.Parent = game.Workspace
return terrain
end
function generateTerrain2()
local terrain = Instance.new(“Model”)
for x = 1, size do
for y = 1, size do
local v = noise((freq*x)/(size), (freq*y)/(size), seed) * power
local p = p:Clone()
p.CFrame = CFrame.new(x, v, y)
p.Parent = terrain
local s = string.format("%d, %d", x, y)
end
end
terrain.Parent = game.Workspace
return terrain
end
function generateTerrain3()
local terrain = Instance.new(“Model”)
for x = 1, size do
for y = 1, size do
local v = noise((freq*x)/(size), (freq*y)/(size), seed) * power
local p = p:Clone()
p.CFrame = CFrame.new(x, v, y)
p.Parent = terrain
local s = string.format("%d, %d", x, y)
print(s)
end
end
terrain.Parent = game.Workspace
return terrain
end
function benchmark(f, n, title)
local tick = tick
local t0 = tick()
for i = 1, n do
local t1 = tick()
wait()
local t = tick()
t0 = t0 + (t - t1)
f()
end
local t = tick()
return string.format("%s:\n\tIterations: %d\n\tTime: %f", title, n, (t-t0))
end
local message = {}
table.insert(message, benchmark(function() generateTerrain1():Destroy() end, 100, “Terrain with no string and no print”))
table.insert(message, benchmark(function() generateTerrain2():Destroy() end, 100, “Terrain with string and no print”))
table.insert(message, benchmark(function() generateTerrain3():Destroy() end, 100, “Terrain with string and print”))
print(table.concat(message, “\n”))
You should probably take the exact results with a huge grain of salt, but I think it’s safe to say that printing all the time is a bad idea. This is in line with Lua Performance Tips from lua.org.