| title | description |
|---|---|
Improve performance |
Lists common performance problems and steps to mitigate them. |
This page describes common performance problems and best practices for mitigating them.
Expensive operations in Luau code take longer to process and can thus impact frame rate. Unless it is being executed in parallel, Luau code runs synchronously and blocks the main thread until it encounters a function that yields the thread.
-
Intensive operations on table structures - Complex operations such as serialization, deserialization, and deep cloning incur a high performance cost, especially on large table structures. This is particularly true if these operations are recursive or involve iterating over very large data structures.
-
High frequency events - Tying expensive operations to frame-based events of
Class.RunServicewithout limiting the frequency means these operations are repeated every frame, which often results in an unnecessary increase in computation time. These events include:Class.RunService.PreAnimationClass.RunService.PreRenderClass.RunService.PreSimulationClass.RunService.PostSimulationClass.RunService.Heartbeat
- Invoke code on
Class.RunServiceevents sparingly, limiting usage to cases where high frequency invocation is essential (for example, updating the camera). You can execute most other code in other events or less frequently in a loop. - Break up large or expensive tasks using
Library.task.wait()to spread the work across multiple frames. - Identify and optimize unnecessarily expensive operations and use multithreading for computationally expensive tasks that don't need to access the data model.
- Certain server-side scripts can benefit from native code generation, a simple flag that compiles a script to machine code rather than bytecode.
| Scope | Associated computation |
|---|---|
| RunService.PreRender | Code executing on the PreRender event |
| RunService.PreSimulation | Code executing on the Stepped event |
| RunService.PostSimulation | Code executing on Heartbeat event |
| RunService.Heartbeat | Code executing on Heartbeat event |
For more information on debugging scripts using the MicroProfiler, see the
Library.debug library, which includes functions for tagging specific code and
further increasing specificity, such as Library.debug.profilebegin and
Library.debug.profileend. Many Roblox API methods called by scripts also have
their own associated MicroProfiler tags that can provide useful signal.
Memory leaks can occur when you write scripts that consume memory that the garbage collector can't properly release when its no longer in use. Leaks are specifically pervasive on the server, because they can continuously be online for many days, whereas a client session is much shorter.
The following memory values in the Developer Console can indicate a problem that needs further investigation:
- LuaHeap - High or growing consumption suggests a memory leak.
- InstanceCount - Consistently growing numbers of instances suggest references to some instances in your code are not being garbage collected.
- PlaceScriptMemory - Provides a script by script breakdown of memory usage.
-
Leaving connections connected - The engine never garbage collects events connected to an instance and any values referenced inside the connected callback. Therefore, active connections of events and code inside the connected instances, connected functions, and referenced values, are out of scope for the memory garbage collector, even after the events are fired.
Although events are disconnected when the instance they belong to is destroyed, a common mistake is to assume this applies to
Class.Playerobjects. After a user leaves an experience, the engine doesn't automatically destroy their representativeClass.Playerobject and character model, so connections to theClass.Playerobject and instances under the character model, such asPlayer.CharacterAdded, still consume memory if you don't disconnect them in your scripts. This can result in very significant memory leaks over time on the server as hundreds of users join and leave the experience. -
Tables - Inserting objects into tables but not removing them when they are no longer needed causes unnecessary memory consumption, especially for tables that track user data when they join. For example, the following code sample creates a table adding user information each time a user joins:
local playerInfo = {} Players.PlayerAdded:Connect(function(player) playerInfo[player] = {} -- some info end)
If you don't remove these entries when they are no longer needed, the table continues to grow in size and consumes more memory as more users join the session. Any code that iterates over this table also becomes more computationally expensive as the table grows in size.
To clean up all used values for preventing memory leaks:
-
Disconnect all connections - Go through your code base make sure each connection is cleaned up via one of the following paths:
- Disconnecting manually using the
Disconnect()function. - Destroying the instance the event belongs to with the
Destroy()function. - Destroying the script object that the connection traces back to.
- Disconnecting manually using the
-
Remove player objects and characters after leaving - Implement code to ensure no connections persist after a user leaves, like in the following example:
Players.PlayerAdded:Connect(function(player) player.CharacterRemoving:Connect(function(character) task.defer(character.Destroy, character) end) end) Players.PlayerRemoving:Connect(function(player) task.defer(player.Destroy, player) end)
Excessive physics simulation can be a key cause of increased computation time per frame on both the server and the client.
-
Excessive physics time step frequency - By default, stepping behavior is in adaptive mode, where physics steps at either 60 Hz, 120 Hz, or 240 Hz, depending on the complexity of the physics mechanism.
A fixed mode with improved accuracy of physics is also available, which forces all physics assemblies to step at 240 Hz (four times per frame). This results in significantly more computation each frame.
-
Excessive number of complexity of simulated objects - The more 3D assemblies that are simulated, the longer physics computations take each frame. Often, experiences will have objects being simulated that do not need to be or will have mechanisms that have more constraints and joints than they need.
-
Overly precise collision detection - Mesh parts have a
Class.MeshPart.CollisionFidelity|CollisionFidelityproperty for detecting collision which offers a variety of modes with different levels of performance impact. Precise collision detection mode for mesh parts has the most expensive performance cost and takes the engine longer to compute.
-
Anchor parts that don't require simulation - Anchor all parts that don't need to be driven by physics, such as for static NPCs.
-
Use adaptive physics stepping - Adaptive stepping dynamically adjusts the rate of physics calculations for physics mechanisms, allowing physics updates to be made less frequently in some cases.
-
Reduce mechanism complexity
- Where possible, minimize the number of physics constraints or joints in an assembly.
- Reduce the amount of self-collision within a mechanism, such as by applying limits or no-collision constraints to ragdoll limbs to prevent them from colliding with each other.
-
Reduce the usage of precise collision fidelity for meshes
-
For small or non-interactable objects where users would rarely notice the difference, use box fidelity.
-
For small-medium size objects, use box or hull fidelities, depending on the shape.
-
For large and very complex objects, build out custom collisions using invisible parts when possible.
-
For objects that don't require collisions, disable collisions and use box or hull fidelity, since the collision geometry is still stored in memory.
-
You can render collision geometry for debug purposes in Studio by toggling on Collision fidelity from the Visualization options widget in the upper‑right corner of the 3D viewport.
Alternatively, you can apply the
CollisionFidelity=PreciseConvexDecompositionfilter to the Explorer which shows a count of all mesh parts with the precise fidelity and allows you to easily select them. -
For an in-depth walkthrough on how to choose a collision fidelity option that balances your precision and performance requirements, see Set physics and rendering parameters.
-
| Scope | Associated computation |
|---|---|
| physicsStepped | Overall physics computation |
| worldStep | Discrete physics steps taken each frame |
Physics movement and collision detection consumes memory. Mesh parts have a Class.MeshPart.CollisionFidelity|CollisionFidelity property that determines the approach that's used to evaluate the collision bounds of the mesh.
The default and precise collision detection modes consumes significantly more memory than the two other modes with lower fidelity collision shapes.
If you see high levels of memory consumption under PhysicsParts, you might need to explore reducing the collision fidelity of objects in your experience.
To reduce memory used for collision fidelity:
- For parts that do not need collisions, disable their collisions by setting
Class.BasePart.CanCollide,Class.BasePart.CanTouchandClass.BasePart.CanQuerytofalse. - Reduce fidelity of collisions using the
Class.MeshPart.CollisionFidelity|CollisionFidelitysetting.Enum.CollisionFidelity.Box|Boxhas the lowest memory overhead, andEnum.CollisionFidelity.Default|DefaultandEnum.CollisionFidelity.Precise|Preciseare generally more expensive.- It's generally safe to set any small anchored part's collision fidelity to
Enum.CollisionFidelity.Box|Box. - For very complex large meshes, you may want to build your own collision mesh out of smaller objects with box collision fidelity.
- It's generally safe to set any small anchored part's collision fidelity to
Class.Humanoid is a class that provides a wide range of functionalities to
player and non player characters (NPCs). Although powerful, a Class.Humanoid
comes with a significant computation cost.
- Leaving all HumanoidStateTypes enabled on NPCs - There is a performance
cost to leaving certain
Enum.HumanoidStateType|HumanoidStateTypesenabled. Disable any that are not needed for your NPCs. For example, unless your NPC is going to climb ladders, it's safe to disable theClimbingstate. - Instantiating, modifying, and respawning models with Humanoids frequently
- This can be intensive for the engine to process, particularly if these models use Layered clothing. This also can be particularly problematic in experiences where avatars respawn often.
- In the MicroProfiler, lengthy updateInvalidatedFastClusters tags (over 4 ms) are often a signal that avatar instantiation/modification is triggering excessive invalidations.
- Using Humanoids in cases where they are not required - Static NPCs that do
not move generally have no need for the
Class.Humanoidclass. - Playing animations on a large number of NPCs from the server - NPC animations that run on the server need to be simulated on the server and replicated to the client. This can be unnecessary overhead.
- Play NPC animations on the client - In experiences with a large number of
NPCs, consider creating the
Class.Animatoron the client and running the animations locally. This reduces the load on the server and the need for unnecessary replication. It also makes additional optimizations possible (such as only playing animations for NPCs who are near to the character). - Use performance-friendly alternatives to Humanoids - NPC models don't
necessarily need to contain a humanoid object.
- For static NPCs, use a simple
Class.AnimationController, because they don't need to move around but just need to play animations. - For moving NPCs, consider implementing your own movement controller and
using an
Class.AnimationControllerfor animations, depending on the complexity of your NPCs.
- For static NPCs, use a simple
- Disable unused humanoid states - Use
Class.Humanoid:SetStateEnabled()to only enable necessary states for each humanoid. - Pool NPC models with frequent respawning - Instead of destroying an NPC completely, send the NPC to a pool of inactive NPCs. This way, when a new NPC is required to respawn, you can simply reactivate one of the NPCs from the pool. This process is called pooling, which minimizes the amount of times characters need to be instantiated.
- Only spawn NPCs when users are nearby - Don't spawn NPCs when users aren't in range, and cull them when users leave their range.
- Avoid making changes to the avatar hierarchy after it is instantiated - Certain modifications to an avatar hierarchy have significant performance implications. Some optimizations are available:
- For custom procedural animations, don't update the
Class.JointInstance.C0andClass.JointInstance.C1properties. Instead, update theClass.Motor6D.Transformproperty. - If you need to attach any
BasePartobjects to the avatar, do so outside of the hierarchy of the avatarModel.
- For custom procedural animations, don't update the
| Scope | Associated computation |
|---|---|
| stepHumanoid | Humanoid control and physics |
| stepAnimation | Humanoid and animator animation |
| updateInvalidatedFastClusters | Associated with instantiating or modifying an avatar |
A significant portion of the time the client spends each frame is on rendering the scene in the current frame. The server doesn't do any rendering, so this section is exclusive to the client.
A draw call is a set of instructions from the engine to the GPU to render something. Draw calls have significant overhead. Generally, the fewer draw calls per frame, the less computational time is spent rendering a frame.
You can see how many draw calls are currently occurring with the Render Stats > Timing item in Studio. You can view Render Stats in the client by pressing ShiftF2.
The more objects that need to be drawn in your scene in a given frame, the more
draw calls are made to the GPU. However, the Roblox Engine utilizes a process
called instancing to collapse identical meshes with the same texture
characteristics into a single draw call. Specifically, multiple meshes with the
same MeshId are handled in a single draw call when:
Class.SurfaceAppearance|SurfaceAppearancesare identical.Class.MeshPart.TextureID|TextureIDsare identical whenClass.SurfaceAppearancedoesn't exist.- Materials are identical when both
Class.SurfaceAppearanceandClass.MeshPart.TextureIDdon't exist.
-
Excessive object density - If a large number of objects are concentrated with a high density, then rendering this area of the scene requires more draw calls. If you are finding your frame rate drops when looking at a certain part of the map, this can be a good signal that object density in this area is too high.
Objects like decals, textures, and particles don't batch well and introduce additional draw calls. Pay extra attention to these object types in a scene. In particular, property changes to
Class.ParticleEmitter|ParticleEmitterscan have a dramatic impact on performance. -
Missed instancing opportunities - Often, a scene will include the same mesh duplicated a number of times, but each copy of the mesh has different mesh or texture asset IDs. This prevents instancing and can lead to unnecessary draw calls.
A common cause of this problem is when an entire scene is imported at once, rather than individual assets being imported into Roblox and then duplicated post-import to assemble the scene.
Even a simple script like this one can help you identify mesh parts with the same name that use different mesh IDs:
local Workspace = game:GetService("Workspace") for _, descendant in Workspace:GetDescendants() do if descendant:IsA("MeshPart") then print(descendant.Name .. ", " .. descendant.MeshId) end end
The output (with Stack Lines enabled) might look something like this. Repeated lines indicate reuse of the same mesh, which is good. Unique lines aren't necessarily bad, but depending on your naming scheme, could indicate duplicate meshes in your experience:
LargeRock, rbxassetid://106420009602747 (x144) -- good LargeRock, rbxassetid://120109824668127 LargeRock, rbxassetid://134460273008628 LargeRock, rbxassetid://139288987285823 LargeRock, rbxassetid://71302144984955 LargeRock, rbxassetid://90621205713698 LargeRock, rbxassetid://113160939160788 LargeRock, rbxassetid://135944592365226 -- all possible duplicates -
Excessive object complexity - Although not as important as the number of draw calls, the number of triangles in a scene does influence how long a frame takes to render. Scenes with a very large number of very complex meshes are a common problem, as are scenes with the
Class.MeshPart.RenderFidelityproperty set toEnum.RenderFidelity.Preciseon too many meshes. -
Excessive shadow casting - Handling shadows is an expensive process, and maps that contain a high number and density of light objects that cast shadows (or a high number and density of small parts influenced by shadows) are likely to have performance issues.
-
High transparency overdraw - Placing objects with partial transparency near each other forces the engine to render the overlapping pixels multiple times, which can hurt performance. For more information on identifying and fixing this issue, see Delete layered transparencies.
- Instancing identical meshes and reducing the amount of unique meshes - If you ensure all identical meshes to have the same underlying asset IDs, the engine can recognize and render them in a single draw call. Make sure to only upload each mesh in a map once and then duplicate them in Studio for reuse rather than importing large maps as a whole, which might cause identical meshes to have separate content IDs and be recognized as unique assets by the engine. Packages are a helpful mechanism for object reuse.
- Culling - Culling describes the process of eliminating draw calls for objects that don't factor into the final rendered frame. By default, the engine skips draw calls for objects outside the camera's field of view (frustum culling) and parts, meshes, and terrain occluded from view by other objects (occlusion culling). In certain scenarios, such as indoor environments, you might be able to implement a room or portal system and manually cull objects to further reduce draw calls or overall computational load.
- Reducing render fidelity - Set render fidelity to Automatic or Performance. This allows meshes to fall back to less complex alternatives, which can reduce the number of polygons that need to be drawn.
- Disabling shadow casting on appropriate parts and light objects - The
complexity of the shadows in a scene can be reduced by selectively disabling
shadow casting properties on light objects and parts. This can be done at edit
time or dynamically at runtime. Some examples are:
-
Use the
This might result in visual artifacts on shadows.Class.BasePart.CastShadowproperty to disable shadow casting on small parts where shadows are unlikely to be visible. This can be particularly effective when only applied to parts that are far away from the user's camera. -
Disable shadows on moving objects when possible.
-
Disable
Class.Light.Shadowson light instances where the object does not need to cast shadows. -
Limit the range and angle of light instances.
-
Use fewer light instances.
-
Consider disabling lights that are outside of a specific range or on a room-by-room basis for indoor environments.
-
| Scope | Associated computation |
|---|---|
| Prepare and Perform | Overall rendering |
| Perform/Scene/computeLightingPerform | Light grid and shadow updates |
| LightGridCPU | Voxel light grid updates |
| ShadowMapSystem | Shadow mapping |
| Perform/Scene/UpdateView | Preparation for rendering and particle updates |
| Perform/Scene/RenderView | Rendering and post processing |
Networking and replication describes the process by which data is sent between the server and connected clients. Information is sent between the client and server every frame, but larger amounts of information require more compute time.
-
Excessive remote traffic - Sending a large amount of data through
Class.RemoteEventorClass.RemoteFunctionobjects or invoking them very frequently can lead to a large amount of CPU time being spent processing incoming packets each frame. Common mistakes include:- Replicating data every frame that does not need to be replicated.
- Replicating data on user input without any mechanism to throttle it.
- Dispatching more data than is required. For example, sending the player's entire inventory when they purchase an item rather than just details of the item purchased.
-
Creation or removal of complex instance trees - When a change is made to the data model on the server, it is replicated to connected clients. This means creating and destroying large instance hierarchies like maps at runtime can be very network intensive.
A common culprit here is the complex animation data saved by Animation Editor plugins in rigs. If these aren't removed before the game is published and the animated model is cloned regularly, a large amount of data will be replicated unnecessary.
-
Server-side TweenService - If
Class.TweenServiceis used to tween an object server side, the tweened property is replicated to each client every frame. Not only does this result in the tween being jittery as clients' latency fluctuates, but it causes a lot of unnecessary network traffic.
You can employ the following tactics to reduce unnecessary replication:
- Avoid sending large amounts of data at once through remote events. Instead, only send necessary data at a lower frequency. For example, for a character's state, replicate it when it changes rather than every frame.
- Chunk up complex instance trees like maps and load them in pieces to distribute the work replicating these across multiple frames.
- Clean up animation metadata, especially the animation directory of rigs, after importing.
- Limit unnecessary instance replication, especially in cases where the
server doesn't need to have knowledge of the instances being created. This
includes:
- Visual effects such as an explosion or a magic spell blast. The server only needs to know the location to determine the outcome, while the clients can create visuals locally.
- First-person item view models.
- Tween objects on the client rather than the server.
| Scope | Associated computation |
|---|---|
| ProcessPackets | Processing for incoming network packets, such as event invocations and property changes |
| Allocate Bandwidth and Run Senders | Outgoing events relevant on servers |
The highest impact mechanism available to creators to improve client memory usage is to enable Instance streaming.
Instance streaming selectively loads out parts of the data model that are not required, which can lead to considerably reduced load time and increase the client's ability to prevent crashes when it comes under memory pressure.
If you are encountering memory issues and have instance streaming disabled, consider updating your experience to support it, particularly if your 3D world is large. Instance streaming is based on distance in 3D space, so larger worlds naturally benefit more from it.
If instance streaming is enabled, you can increase the aggressiveness of it. For example, consider:
- Reducing use the persistent StreamingIntegrity.
- Reducing the streaming radius.
For more information on streaming options and their benefits, see Streaming properties.
- Asset duplication - A common mistake is to upload the same asset multiple times resulting in different asset IDs. This can lead to the same content being loaded into memory multiple times.
- Excessive asset volume - Even when assets are not identical, there are cases when opportunities to reuse the same asset and save memory are missed.
- Audio files - Audio files can be a surprising contributor to memory usage, particularly if you load all of them into the client at once rather than only loading what you need for a portion of the experience. For strategies, see Load times.
- High resolution textures - Graphics memory consumption for a texture is unrelated to the size of the texture on the disk, but rather the number of pixels in the texture.
- For example, a 1024x1024 pixel texture consumes four times the graphics memory of a 512x512 texture.
- Images uploaded to Roblox are transcoded to a fixed format, so there is no memory benefit to uploading images in a color model associated with fewer bytes per pixel. Similarly, compressing images prior to upload or removing the alpha channel from images that don't need it can decrease image size on disk, but either doesn't improve or only minimally improves memory usage. Though the engine automatically downscales texture resolution on some devices, the extent of the downscale depends on the device characteristics, and excessive texture resolution can still cause problems.
- You can identify the graphics memory consumption for a given texture by expanding the GraphicsTexture category in the Developer Console.
-
Only upload assets once - Reuse the same asset ID across objects and ensure the same assets, especially meshes and images, aren't uploaded separately multiple times.
-
Find and fix duplicate assets - Look for identical mesh parts and textures that are uploaded multiple times with different IDs.
- Though there is no API to detect similarity of assets automatically, you can collect all the image asset IDs in your place (either manually or with a script), download them, and compare them using external comparison tools.
- For mesh parts, the best strategy is to take unique mesh IDs and organize them by size to manually identify duplicates.
- Instead of using separate textures for different colors, upload a single texture and use the
Class.SurfaceAppearance.Colorproperty to apply various tints to it.
-
Import assets in map separately - Instead of importing an entire map at once, import and reconstruct assets in the map individually and reconstruct them. The 3D importer doesn't do any de-duplication of meshes, so if you were to import a large map with a lot of separate floor tiles, each of those tiles would be imported as a separate asset (even if they are duplicates). This can lead to performance and memory issues down the line, as each mesh is treated as individually and takes up memory and draw calls.
-
Limit the pixels of images to no more than the necessary amount. Unless an image is occupying a large amount of physical space on the screen, it usually needs at most 512x512 pixels. Most minor images should be smaller than 256x256 pixels.
-
Use trim sheets to ensure maximum texture reuse in 3D maps. For steps and examples on how to create trim sheets, see Create trim sheets.
You might also consider using sprite sheets to load many smaller UI images as a single image. You can then use
Class.ImageLabel.ImageRectOffsetandClass.ImageLabel.ImageRectSizeto display portions of the sheet.
Many experiences implement custom loading screens and use the Class.ContentProvider:PreloadAsync() method to request assets so that images, sounds, and meshes are downloaded in the background.
The advantage of this approach is that it lets you ensure important parts of your experience are fully loaded without pop-in. However, a common mistake is overutilizing this method to preload more assets than are actually required.
An example of a bad practice is loading the entire Class.Workspace. While this might prevent texture pop-in, it significantly increases load time.
Instead, only use Class.ContentProvider:PreloadAsync() in necessary situations, which include:
- Images in the loading screen.
- Important images in your experience menu, such as button backgrounds and icons.
- Important assets in the starting or spawning area.
If you must load a large number of assets, we recommend you provide a Skip Loading button.