Memory graph displaying sawtooth shape w/ custom memory category

I don’t know if it’s the cause, but the incorrect graph in the screenshot is of a custom memory category. It’s being set and reset every frame.

A sawtooth pattern is being displayed instead of the usual graph of memory usage over time (all other categories I checked were fine).

The sawtooth pattern occurs consistently in my game, so it’s not a random occurrence. Initially it starts out positive:

However as things get more intensive, the pattern changes direction and becomes negative (the initial pattern in the graph appears flat because the scale suddenly changed):

Sorry, that’s all the information I have.

EDIT: Forgot to mention, this only happens on the desktop client. Does not happen in Studio or on mobile.
image

2 Likes

I will need more information about the code setup that makes such a graph.

1 Like

I will try to summarise it.

The memory categories are used in a ModuleScript (I will call “BadModule”) where most exported functions set and reset the memory category. There are some exceptions which don’t set it, but that’s because they’re small and bounded and not interesting. Memory category is also set during initialization so we can track stuff created then. There are two exported functions which are called every frame, one sets the memory category and the other does not. This bug is for client-side memory, since this module does not run on the server.

-- Requiring other modules here, before we set memory category.

local MEM_CAT = "BadModule"

-- Set memory category so stuff created during initialization gets tracked.
debug.setmemorycategory(MEM_CAT)

local State = {
   -- Many members here corresponding to dynamic state the module uses.
   -- Linked list sentinels, numbers, sorted tables, etc.
}

-- Lots of local functions, read-only variables and tables, etc. here

function Module.Something()
   debug.setmemorycategory(MEM_CAT)
   ...
   debug.resetmemorycategory()
end

function Module.RenderSteppedFirst()
   -- Not setting memory category because we don't do much here.
   ...
end

function Module.ClientHeartbeatLast()
   debug.setmemorycategory(MEM_CAT)
   ...
   debug.resetmemorycategory()
end

debug.resetmemorycategory()
return Module

The ClientHeartbeat and RenderStepped functions get called every frame by a god script (of sorts) which controls the ordering of all such pieces of code:

RunService:BindToRenderStepped("BadModule", Enum.RenderPriority.First.Value-1, function()
   -- Nothing is done before (in this event callback)
   BadModule.RenderSteppedFirst()
end)

RunService.RenderStepped:Connect(function()
   Module1.RenderStepped()
end)

RunService.Heartbeat:Connect(function()
   Module1.ClientHeartbeat()
   Module2.ClientHeartbeat()

   BadModule.ClientHeartbeatLast()
   -- Nothing is done after (in this event callback).
end)

Callbacks from remotes and user input may also end up call functions within BadModule, but those don’t happen much - if at all - before the bug starts occurring, so those probably don’t matter.

Here are the things that the module does every frame while under the memory category:

  • debug.profilebegin(), debug.profileend(), and os.clock() are called a lot
  • Creating small temporary tables which will be cleaned up when we exit the scope
  • Sorting (usually small) arrays with table.sort() and a custom sorting function
  • For and while loops which do nothing because there is nothing to iterate over.
  • Writing to existing keys in the State table e.g. overwriting a number with another number.
  • Reads and occasionally writes data that resides outside of the module e.g. tables related to players. (Again mostly just overwriting existing keys with a different value of the same type.)

The module does a lot of other stuff, but the bug occurs before they have been done. I’ll mention them here anyway. These things do seem to affect the direction of the sawtooth pattern and the scale of the graph.

  • Creating potentially hundreds of thousands of tables, and does a lot of things with them including linked list operations, setting members, etc.
  • It creates Instances, parents them to the DataModel, and sets properties.
1 Like