mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 14:01:37 +00:00
rules
This commit is contained in:
parent
a9b4f8e83b
commit
e6efc04756
8494 changed files with 495610 additions and 0 deletions
60
.cursor/rules/roblox/performance-optimization/design.md
Normal file
60
.cursor/rules/roblox/performance-optimization/design.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Design for performance
|
||||
description: Outlines performance best practices to follow as you build a new experience.
|
||||
---
|
||||
|
||||
Designing for performance means following a handful of best practices **as you build** your experience. Compared to finding and fixing performance issues later in the development process, designing for performance early can save you a lot of time and effort.
|
||||
|
||||
## Low-end devices
|
||||
|
||||
Lower-end devices, particularly mobile devices, have severe memory limitations and are susceptible to crashes due to out of memory (OOM) errors:
|
||||
|
||||
- If you want to support lower-end devices, choose at least one "baseline" device, test your experience on it throughout the development process, and pay close attention to frame rate and memory usage. As you find problem areas in your experience, use those areas to identify the limits of your device.
|
||||
|
||||
For example, you might test an experience with the **Render** (<kbd>Shift</kbd><kbd>F2</kbd>) and **Summary** (<kbd>Shift</kbd><kbd>F2</kbd>) debug stats enabled. If the frame rate starts to drop in a particularly cluttered area, you could examine the **Draw (scene)** numbers and determine that you need to stay below 1,000 draw calls and 1,000,000 triangles for the experience to run well on your baseline device.
|
||||
|
||||
Or you could examine the **Developer Console** (<kbd>F9</kbd>) and note that memory usage is a bit high unless you enable [streaming](../workspace/streaming.md). Having a clear understanding of device limits can help you stay under them as you continue to build your experience.
|
||||
|
||||

|
||||
|
||||
- The device emulator in Roblox Studio is useful for checking aspect ratio and controls, but isn't accurate for memory usage; when you test an experience in Studio, it runs the server and the client, so memory usage is significantly higher.
|
||||
|
||||
<Alert severity="info">
|
||||
Roblox does not have access to all of a device's memory. Some amount is required for the operating system and other applications.
|
||||
</Alert>
|
||||
|
||||
More generally, testing on a variety of devices can help you check that the experience matches your visual and performance expectations at different graphics quality levels. For a much more detailed example of how you might think about optimizing your experience for low-end mobile devices, see [Real World Building and Scripting Optimization](https://devforum.roblox.com/t/real-world-building-and-scripting-optimization-for-roblox/3127146).
|
||||
|
||||
## Streaming and teleportation
|
||||
|
||||
- [Instance streaming](../workspace/streaming.md) lets Roblox dynamically load and unload 3D content and is a great option for most places, especially larger ones. Streaming improves join times, reduces memory footprint, and increases frame rate. For more information, see [Improve performance](improve.md#instance-streaming).
|
||||
|
||||
- Consider breaking large places into more manageable ones and using [teleportation](../projects/teleport.md) to move players between them. This approach can reduce **initial** join times, but imposes additional join times as players teleport from place to place. Benefits to memory usage vary depending on the size of the place and whether you've enabled streaming.
|
||||
|
||||
Even ignoring performance considerations, you might find that having multiple places simplifies the development process, especially if you regularly add new content to your experience or are part of a larger team.
|
||||
|
||||
## Materials and duplication
|
||||
|
||||
- Built-in materials use far less memory than custom textures, but might not match your artistic vision. Try to use materials whenever possible in order to conserve memory budget for the textures that are central to your experience.
|
||||
|
||||
- As you create assets, convert them into [packages](../projects/assets/packages.md). Making packages part of your workflow helps avoid the common issue of duplicate assets with different IDs, which can hurt performance.
|
||||
|
||||
- When you add meshes and textures, use and reuse them rather than importing duplicate copies. By resizing, rotating, and overlapping, you can create rich, varied environments that require very few [draw calls](improve.md#draw-calls). For more information, see [Remove duplicate textures](../tutorials/curriculums/environmental-art/optimize-your-experience.md#remove-duplicate-textures).
|
||||
|
||||
## Transparency
|
||||
|
||||
- Avoid transparency values other than 0 (visible) and 1 (invisible). When you use partial transparency, be especially careful to avoid [high transparency overdraw](../tutorials/curriculums/environmental-art/optimize-your-experience.md#delete-layered-transparencies).
|
||||
|
||||
## Scripting
|
||||
|
||||
- Whenever possible, write event-driven code rather than per-frame calculations. At 60 FPS, the total budget for each frame is 16.67 milliseconds (ms). Even seemingly minor per-frame calculations can use a significant portion of that budget.
|
||||
|
||||
- Find ways to break long-running code into manageable chunks. If a piece of code takes 100 ms to execute and you run it every frame, your experience can only run at 10 FPS. If you decide to only run the code once per second in an experience that otherwise runs at 60 FPS, 59 of your frames arrive after 16.67 ms... and then one after 100 ms, which causes a jarring stutter.
|
||||
|
||||
Instead, investigate how you can break up the code. Maybe you can perform 5 ms of work per frame, use `Library.task.wait()`, and have the completed calculation every 20 frames while still maintaining 60 FPS. [Multithreading](../scripting/multithreading.md), sometimes called Parallel Luau, can also help.
|
||||
|
||||
- Use the `Datatype.RBXScriptConnection:Disconnect()` method to stop functions from being called unnecessarily the next time an event fires.
|
||||
|
||||
- Don't call the same method every time you need a value. Call the method once, store the value, and then overwrite it later as necessary.
|
||||
|
||||
- Don't store everything in `Class.ReplicatedStorage`. The client loads everything that is in this container. Instead, use `Class.ServerStorage` for anything that the client does not need access to.
|
||||
155
.cursor/rules/roblox/performance-optimization/identify.md
Normal file
155
.cursor/rules/roblox/performance-optimization/identify.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
title: Identify performance issues
|
||||
description: Explains how to use Roblox tools to identify performance issues.
|
||||
---
|
||||
|
||||
Identifying performance issues generally means drilling down on one of three categories: frame rate (compute), memory, or load time.
|
||||
|
||||
Roblox has a number of tools for identifying performance issues, some of which are available in the Roblox client (and therefore Studio) and some of which are only available in Studio. Whereas you can observe client behavior directly, you must use tools to diagnose server issues.
|
||||
|
||||
## Diagnostic Tools
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Tool</th>
|
||||
<th>Description</th>
|
||||
<td>Location</td>
|
||||
<th>Keyboard shortcut</th>
|
||||
<th>Environment</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Developer Console](../studio/developer-console.md)</td>
|
||||
<td>A console with real-time log messages, errors, and detailed information on memory, networking, and script performance. It's useful for viewing memory consumption, key server health stats, script performance stats, and to launch the client or server MicroProfiler.</td>
|
||||
<td>Studio, in-experience</td>
|
||||
<td><kbd>F9</kbd></td>
|
||||
<td>Live sessions, Studio testing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[MicroProfiler](./microprofiler/index.md)</td>
|
||||
<td>A debugging tool that breaks down how much computational time is spent on tasks each frame. It can generate dumps for analysis showing a precise breakdown of individual frames, making it helpful for identifying the specific task causing performance issues. You can run the profiler on either the client or the server, depending on which side has an issue.</td>
|
||||
<td>Studio, in-experience</td>
|
||||
<td><kbd><kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F6</kbd> (<kbd>⌘</kbd><kbd>⌥</kbd><kbd>F6</kbd>)</kbd></td>
|
||||
<td>Live sessions, Studio testing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Performance Stats bar</td>
|
||||
<td>A toolbar with basic performance statistics, including memory consumption, CPU, GPU, network data sent and received, and ping time.</td>
|
||||
<td>In-experience</td>
|
||||
<td><kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F7</kbd> (<kbd>⌘</kbd><kbd>⌥</kbd><kbd>F7</kbd>)</td>
|
||||
<td>Live sessions, Studio testing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Debug Stats</td>
|
||||
<td>Overlays with detailed information around graphics, physics, network traffic, and FPS.</td>
|
||||
<td>Studio, in-experience</td>
|
||||
<td><kbd>Shift</kbd><kbd>F1</kbd>, <kbd>Shift</kbd><kbd>F2</kbd>, <kbd>Shift</kbd><kbd>F3</kbd>, <kbd>Shift</kbd><kbd>F4</kbd>, <kbd>Shift</kbd><kbd>F5</kbd></td>
|
||||
<td>Live sessions, Studio testing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Performance Dashboard](../production/analytics/performance.md)</td>
|
||||
<td>A dashboard with aggregate charts for client and server memory usage, client frame rate, server heartbeat, and crash rates in real time. It's helpful for analyzing performance patterns over time. See [Monitoring Performance](./monitor.md).</td>
|
||||
<td>Creator Dashboard</td>
|
||||
<td>N/A</td>
|
||||
<td>Live sessions</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Server compute
|
||||
|
||||
Server heartbeat is capped at 60 FPS for all experiences, so lower values might indicate a performance issue. To check server heartbeat:
|
||||
|
||||
- With the [Developer Console](../studio/developer-console.md) - In the **Server Jobs** tab, expand the **Heartbeat** row and check the **Steps Per Sec** value, which represents the heartbeat of your experience.
|
||||
|
||||
- With the [MicroProfiler](./microprofiler/index.md), you can check the graph to see if the frame takes longer than 16.67 ms.
|
||||
|
||||
<Alert severity="info">
|
||||
The MicroProfiler is particularly useful for identifying "spikes" in performance, where some frames take significantly longer than others to process.
|
||||
</Alert>
|
||||
|
||||
Another symptom of degraded server heartbeat is increased latency (commonly known as ping). The longer the server takes to finish computing its tasks each frame, the longer it takes to process network data sent and received from clients. To check average ping for all players connected to a server, go to the **Server Stats** tab in the [Developer Console](../studio/developer-console.md).
|
||||
|
||||
## Client compute
|
||||
|
||||
The default client frame rate cap is 60 FPS. However, users can raise their frame rate cap up to 240 FPS on Windows.
|
||||
|
||||
Frame rate differs wildly between devices. For example, a high-end PC might be able to "brute force" a computational issue and only experience an imperceptible frame rate dip. If you test on lower-end devices, problems tend to be more severe and thus easier to notice.
|
||||
|
||||
To check the frame rate of your experience:
|
||||
|
||||
- In the client, press <kbd>Shift</kbd><kbd>F5</kbd> to show the debug stats summary.
|
||||
- From Studio's **Window** ⟩ **Performance** menu, toggle on **Stats** to enable debug stats.
|
||||
|
||||
<Alert severity="warning">
|
||||
Performance stats in Studio are skewed by the Studio application, so you should view the frame rate on the client if you have a production experience.
|
||||
</Alert>
|
||||
|
||||
- With the [MicroProfiler](./microprofiler/index.md), you can check the graph to see if the frame takes longer than 16.67 ms.
|
||||
|
||||
<Alert severity="info">
|
||||
When evaluating frame rate, it can help to set the graphics quality to its maximum value to remove the effect of the frame rate manager. In the client, open the menu, click **Settings**, change **Graphics Mode** to manual, and raise graphics quality.
|
||||
</Alert>
|
||||
|
||||
## Server memory
|
||||
|
||||
Try to keep server memory usage below 50%. Total server memory uses the following formula:
|
||||
|
||||
`6.25 GiB + (100 MiB * largest_number_of_connected_players)`
|
||||
|
||||
<Alert severity="info">
|
||||
These numbers use powers of 2, so 1 GiB refers to 2^30 bytes and 1 MiB to 2^20 bytes.
|
||||
</Alert>
|
||||
|
||||
For example, a server with 30 connected players has approximately 9.18 GiB of total memory. Servers gain memory when players connect, but **don't** lose it when players disconnect. If 10 players leave, the server still has 9.18 GiB of memory rather than shrinking to 8.2 GiB.
|
||||
|
||||
When servers shut down (for example, when they are empty or as part of the [update process](../projects/update-experiences.md)), their replacements start with the base amount of memory and begin scaling up as players connect.
|
||||
|
||||
## Client memory
|
||||
|
||||
There are several ways to check memory usage for an experience:
|
||||
|
||||
1. Open the [Developer Console](../studio/developer-console.md) and switch to the **Memory** tab. This tab gives a breakdown of how memory is being allocated. Use the client rather than Studio to get the most accurate readouts.
|
||||
2. Enable the **Performance Stats** view from the settings menu in the client to see an overlay with total client memory usage.
|
||||
|
||||
High memory usage is not necessarily indicative of a problem, but some indications that you may need to investigate more are:
|
||||
|
||||
- A significant percentage of client crashes showing in the **Performance Dashboard**, particularly a sudden uptick that coincides with an update. Some number of crashes are expected, but you should investigate if your crash rates increase above 2-3%.
|
||||
- A crash occurs while testing on a device that you want your experience to support.
|
||||
|
||||
A significant portion of an experience's memory consumption on the client are from assets, such as images, meshes, and audio files, loaded into memory. In the Developer Console, check the following labels under **PlaceMemory**:
|
||||
|
||||
- **GraphicsMeshParts** - Graphics memory consumed by meshes.
|
||||
- **GraphicsTexture** - Graphics memory consumed by textures.
|
||||
- **Sounds** - Memory consumed by audio files.
|
||||
|
||||
## Load times
|
||||
|
||||
There are no built-in tools for checking load times, but because they don't require millisecond-level precision, a stopwatch is usually all you need to understand your current baseline and check whether you made a substantive improvement. You can use a client script in `Class.ReplicatedFirst` to get some sense of how your changes impact load times, but this script doesn't provide a complete, end-to-end measurement:
|
||||
|
||||
```lua
|
||||
local startTime = os.clock()
|
||||
|
||||
game.Loaded:Connect(function()
|
||||
local loadTime = os.clock() - startTime
|
||||
local roundedLoadTime = math.round(loadTime * 10000) / 10000 -- four decimal places
|
||||
print("Game loaded in " .. roundedLoadTime .. " seconds.")
|
||||
print("Number of instances loaded: " .. #workspace:GetDescendants())
|
||||
end)
|
||||
```
|
||||
|
||||
For additional insight, enable **Print Join Size Breakdown** from the **Network** tab of [Studio Settings](../studio/setup.md#customization) to print the top 20 instances by size and a percentage breakdown by instance type when you start the experience in Studio.
|
||||
|
||||
## Ping
|
||||
|
||||
The two primary measurements of ping (latency) are **network ping** and **data ping**.
|
||||
|
||||
Network ping is the round-trip time measured from when the client sends a network data packet to when it receives the server's echo reply. It is similar to what would be reported as `time` by the general purpose ping network utility.
|
||||
|
||||
Network ping is visible in the **Performance Stats** bar. It is mostly affected by the length of the network path between the client and server and is outside your control.
|
||||
|
||||
Data ping is the round-trip time measured from when the client sends data reliably through the replication system to when it receives an echo reply from the server's replication system. It includes time spent in both the client's and server's replication queues, along with TCP-like retransmissions of lost or corrupted packets.
|
||||
|
||||
Data ping is similar to sending a reliable remote event from the client, receiving it on the server, and then sending a reliable remote event from the server to the client in response. Because data ping also includes time spent on the network between the client and server, it is greater than or equal to network ping.
|
||||
|
||||
Average data ping for all players connected to a server is visible in the **Server Stats** tab of the Developer Console. Data ping for an individual client is visible using <kbd>Shift</kbd><kbd>F3</kbd> to display network debug stats.
|
||||
|
||||
Data ping is affected by both the network path and by replication queueing delays. When the server generates replication data faster than the network can send it (or faster than the client can process it), data must wait in replication queues. Some difference between data ping and network ping is normal due to both network retransmissions and the way network data is processed by the Roblox app. However, if data ping is significantly higher than network ping, use the [MicroProfiler](./microprofiler/network.md) to view network traffic. For optimization suggestions, see [Networking and replication](./improve.md#networking-and-replication).
|
||||
595
.cursor/rules/roblox/performance-optimization/improve.md
Normal file
595
.cursor/rules/roblox/performance-optimization/improve.md
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
---
|
||||
title: Improve performance
|
||||
description: Lists common performance problems and steps to mitigate them.
|
||||
---
|
||||
|
||||
This page describes common performance problems and best practices for mitigating them.
|
||||
|
||||
## Script computation
|
||||
|
||||
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.
|
||||
|
||||
### Common problems
|
||||
|
||||
- **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.RunService` without 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.PreAnimation`
|
||||
- `Class.RunService.PreRender`
|
||||
- `Class.RunService.PreSimulation`
|
||||
- `Class.RunService.PostSimulation`
|
||||
- `Class.RunService.Heartbeat`
|
||||
|
||||
### Mitigation
|
||||
|
||||
- Invoke code on `Class.RunService` events 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](../scripting/multithreading.md) for computationally
|
||||
expensive tasks that don't need to access the data model.
|
||||
- Certain server-side scripts can benefit from [native code generation](../luau/native-code-gen.md), a simple flag that compiles a script to machine code rather than bytecode.
|
||||
|
||||
### MicroProfiler scopes
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Scope</th>
|
||||
<th>Associated computation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RunService.PreRender</td>
|
||||
<td>Code executing on the PreRender event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RunService.PreSimulation</td>
|
||||
<td>Code executing on the Stepped event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RunService.PostSimulation</td>
|
||||
<td>Code executing on Heartbeat event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RunService.Heartbeat</td>
|
||||
<td>Code executing on Heartbeat event</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.
|
||||
|
||||
## Script memory usage
|
||||
|
||||
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.
|
||||
|
||||
### Common problems
|
||||
|
||||
- **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.Player`
|
||||
objects. After a user leaves an experience, the engine doesn't automatically
|
||||
destroy their representative `Class.Player` object and character model, so
|
||||
connections to the `Class.Player` object and instances under the character
|
||||
model, such as `Class.Player.CharacterAdded|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:
|
||||
|
||||
```lua title="Example"
|
||||
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.
|
||||
|
||||
### Mitigation
|
||||
|
||||
To clean up all used values for preventing memory leaks:
|
||||
|
||||
- **Disconnect all connections** - Go through your codebase and make sure each
|
||||
connection is cleaned up via one of the following paths:
|
||||
- Disconnecting manually using the `Datatype.RBXScriptConnection:Disconnect()|Disconnect()` function.
|
||||
- Destroying the instance the event belongs to with the `Class.Instance:Destroy()|Destroy()` function.
|
||||
- Destroying the script object that the connection traces back to.
|
||||
|
||||
- **Remove player objects and characters after leaving** - Enable `Class.Workspace.PlayerCharacterDestroyBehavior` to automatically destroy player objects and character models after a user leaves. If you prefer, you can instead clean them up manually:
|
||||
|
||||
```lua title="Example player and character cleanup"
|
||||
local Players = game:GetService("Players")
|
||||
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)
|
||||
```
|
||||
|
||||
## Physics computation
|
||||
|
||||
Excessive physics simulation can be a key cause of increased computation time
|
||||
per frame on both the server and the client.
|
||||
|
||||
### Common problems
|
||||
|
||||
- **Excessive physics time step frequency** - By default, stepping behavior is
|
||||
in [adaptive mode](../physics/adaptive-timestepping.md), 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|CollisionFidelity` property 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.
|
||||
|
||||
### Mitigation
|
||||
|
||||
- **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](../studio/ui-overview.md#visualization-options) widget in the upper‑right corner of the 3D viewport.
|
||||
|
||||
Alternatively, you can apply the `CollisionFidelity=PreciseConvexDecomposition` filter to the [Explorer](../studio/explorer.md#property-filters) 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](../tutorials/curriculums/environmental-art/assemble-an-asset-library.md#collisionfidelity).
|
||||
|
||||
### MicroProfiler scopes
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Scope</th>
|
||||
<th>Associated computation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>physicsStepped</td>
|
||||
<td>Overall physics computation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>worldStep</td>
|
||||
<td>Discrete physics steps taken each frame</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Physics memory usage
|
||||
|
||||
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.
|
||||
|
||||
### Common problem
|
||||
|
||||
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](../workspace/collisions.md#mesh-and-solid-model-collisions) of objects in your experience.
|
||||
|
||||
### How to mitigate
|
||||
|
||||
To reduce memory used for collision fidelity:
|
||||
|
||||
- For parts that do not need collisions, disable their collisions by setting `Class.BasePart.CanCollide`, `Class.BasePart.CanTouch` and `Class.BasePart.CanQuery` to `false`.
|
||||
- Reduce fidelity of collisions using the `Class.MeshPart.CollisionFidelity|CollisionFidelity` setting. `Enum.CollisionFidelity.Box|Box` has the lowest memory overhead, and `Enum.CollisionFidelity.Default|Default` and `Enum.CollisionFidelity.Precise|Precise` are 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.
|
||||
|
||||
## Humanoids
|
||||
|
||||
`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.
|
||||
|
||||
### Common problems
|
||||
|
||||
- **Leaving all HumanoidStateTypes enabled on NPCs** - There is a performance
|
||||
cost to leaving certain `Enum.HumanoidStateType|HumanoidStateTypes`
|
||||
enabled. Disable any that are not needed for your NPCs. For
|
||||
example, unless your NPC is going to climb ladders, it's safe to disable
|
||||
the `Enum.HumanoidStateType.Climbing|Climbing` state.
|
||||
- **Instantiating, modifying, and respawning models with `Class.Humanoid|Humanoids` or [skinned](../art/modeling/rigging.md) `Class.MeshPart|MeshParts` 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.Humanoid` class.
|
||||
- **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.
|
||||
- **Performing unnecessary size and scale changes** - Size/scale changes cause FastCluster to be rebuilt. Try to reduce this during gameplay if you are seeing FastCluster-related performance issues. Similarly, other property changes might also cause FastCluster to be rebuilt, so in general reduce these changes as much as possible.
|
||||
|
||||
### Mitigation
|
||||
|
||||
- **Play NPC animations on the client** - In experiences with a large number of
|
||||
NPCs, consider creating the `Class.Animator` on 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.AnimationController` for animations, depending on the
|
||||
complexity of your NPCs.
|
||||
- **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.C0` and `Class.JointInstance.C1` properties. Instead, update the `Class.Motor6D.Transform` property.
|
||||
- If you need to attach any `Class.BasePart` objects to the avatar, do so outside the hierarchy of the avatar `Class.Model`.
|
||||
|
||||
### MicroProfiler scopes
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Scope</th>
|
||||
<th>Associated computation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stepHumanoid</td>
|
||||
<td>Humanoid control and physics</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>stepAnimation</td>
|
||||
<td>Humanoid and animator animation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>updateInvalidatedFastClusters</td>
|
||||
<td>Associated with instantiating or modifying an avatar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Rendering
|
||||
|
||||
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.
|
||||
|
||||
### Draw calls
|
||||
|
||||
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 <kbd>Shift</kbd><kbd>F2</kbd>.
|
||||
|
||||
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 `Class.MeshPart.MeshContent|MeshContent` are handled in a single draw call when:
|
||||
|
||||
- `Class.SurfaceAppearance|SurfaceAppearances` are identical if present, otherwise when `Class.MeshPart.TextureContent|TextureContents` are identical.
|
||||
- Materials are identical when both `Class.SurfaceAppearance` and
|
||||
`Class.MeshPart.TextureID` don't exist.
|
||||
|
||||
### Other common problems
|
||||
|
||||
- **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|ParticleEmitters` can 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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
|
||||
```text
|
||||
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.RenderFidelity` property set
|
||||
to `Enum.RenderFidelity.Precise|Precise` on 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) can 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](../tutorials/curriculums/environmental-art/optimize-your-experience.md#delete-layered-transparencies).
|
||||
|
||||
- **Unnecessary skinned MeshPart movement** - Skinned MeshParts that are part of a Model without a Humanoid are grouped using spatially-organized FastClusters. When these MeshParts move, they must be continually added to and removed from these spatial clusters, forcing the clusters to be rebuilt and impacting performance.
|
||||
- A highly effective workaround is to embed a Humanoid within the Model. The presence of a Humanoid overrides the default spatial clustering behavior, mandating the use of a single, unified FastCluster for the entire Model. Consequently, position updates no longer necessitate cluster rebuilds, thus mitigating the performance bottleneck. This technique should be reserved exclusively for MeshParts with expected movement, as it may introduce memory overhead and negate the benefits of spatial optimization. We recommend always profile your experience after making these types of changes. See [Humanoid performance tips](#humanoids) for additional information.
|
||||
- **Too many parts in a `Class.Model`** - Too many parts in a Model could cause rebuilds more often due to the potential for a part's property to change leading to requiring a full rebuild. Find the right balance of parts in a Model when it is using FastCluster.
|
||||
|
||||
### Mitigation
|
||||
|
||||
- **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](../projects/assets/packages.md) 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 `Class.MeshPart.RenderFidelity` to `Enum.RenderFidelity.Automatic|Automatic` or `Enum.RenderFidelity.Performance|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
|
||||
Roblox engine automatically degrades shadow quality as client graphics quality
|
||||
level decreases, eventually disabling shadows altogether at quality levels
|
||||
below 4. However, you can selectively disable shadow casting properties on
|
||||
light objects and parts to improve performance while shadows are enabled and
|
||||
increase the likelihood that shadows remain enabled. Some examples of
|
||||
optimizations you can make either at edit time or dynamically at runtime:
|
||||
- Use the `Class.BasePart.CastShadow` property to disable shadow casting on
|
||||
small parts where shadows are unlikely to be visible. This strategy is
|
||||
particularly effective when applied to parts that are far away from the
|
||||
user's camera.
|
||||
|
||||
<Alert severity="warning">
|
||||
This might result in visual artifacts on shadows.
|
||||
</Alert>
|
||||
|
||||
- Disable shadows on moving objects when possible.
|
||||
- Disable `Class.Light.Shadows` on 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.
|
||||
|
||||
### MicroProfiler scopes
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Scope</th>
|
||||
<th>Associated computation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare and Perform</td>
|
||||
<td>Overall rendering</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/computeLightingPerform</td>
|
||||
<td>Light grid and shadow updates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LightGridCPU</td>
|
||||
<td>Voxel light grid updates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ShadowMapSystem</td>
|
||||
<td>Shadow mapping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/UpdateView</td>
|
||||
<td>Preparation for rendering and particle updates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/RenderView</td>
|
||||
<td>Rendering and post processing</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Networking and replication
|
||||
|
||||
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.
|
||||
|
||||
### Common problems
|
||||
|
||||
- **Excessive remote traffic** - Sending a large amount of data through
|
||||
`Class.RemoteEvent` or `Class.RemoteFunction` objects 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 experience is published and
|
||||
the animated model is cloned regularly, a large amount of data will be
|
||||
replicated unnecessary.
|
||||
|
||||
- **Server-side TweenService** - If `Class.TweenService` is 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.
|
||||
|
||||
### Mitigation
|
||||
|
||||
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.
|
||||
|
||||
### MicroProfiler scopes
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Scope</th>
|
||||
<th>Associated computation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ProcessPackets</td>
|
||||
<td>Processing for incoming network packets, such as event invocations and property changes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allocate Bandwidth and Run Senders</td>
|
||||
<td>Outgoing events relevant on servers</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Asset memory usage
|
||||
|
||||
The highest impact mechanism available to creators to improve client memory usage is to enable [Instance streaming](../workspace/streaming.md).
|
||||
|
||||
### Instance streaming
|
||||
|
||||
Instance streaming selectively loads out parts of the data model that are not required, which can lead to considerably reduced load times 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 of `Enum.ModelStreamingMode.Persistent` where possible. You may need to update your scripts if you're using it as a compatibility measure.
|
||||
- Reducing the `Workspace.StreamingMinRadius` and `Workspace.StreamingTargetRadius`.
|
||||
|
||||
For more information on streaming options and their benefits, see [Streaming properties](../workspace/streaming.md#streaming-properties).
|
||||
|
||||
### Other common problems
|
||||
|
||||
- **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](#load-times).
|
||||
- **High resolution textures** - Graphics memory consumption for a texture is unrelated to the size of the texture on the disk; the number of pixels in the texture determines memory usage. 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 doesn't improve memory usage.
|
||||
|
||||
As an experience loads, the engine automatically starts with lower quality textures and then ramps up quality based on available device memory, distance from the camera, amount of screen-space that the texture takes up, and other factors. Even still, strategically sizing your textures can improve memory usage in your experience.
|
||||
|
||||
### Mitigation
|
||||
|
||||
- **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.Color` property 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](../resources/beyond-the-dark/building-architecture.md#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.ImageRectOffset` and `Class.ImageLabel.ImageRectSize` to display portions of the sheet.
|
||||
|
||||
## Load times
|
||||
|
||||
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 times.
|
||||
|
||||
Another similar practice is utilising `Class.ContentProvider.RequestQueueSize` to ensure that all requested assets have finished loading. However, this presents the same issue of significantly increased load times, while also being an unreliable method due to its fluctuating nature.
|
||||
|
||||
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.
|
||||
45
.cursor/rules/roblox/performance-optimization/index.md
Normal file
45
.cursor/rules/roblox/performance-optimization/index.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Performance optimization
|
||||
description: Introduces the performance optimization process.
|
||||
---
|
||||
|
||||
Performance optimization is the process of building and iterating on an experience to improve frame rate, memory usage, and load times. Players expect experiences to perform well, so taking the time to understand and improve your experience's performance can be critical to its success.
|
||||
|
||||
The first step in performance optimization is to [design for performance](design.md), which means following a set of best practices **as you build** a new experience. After you publish an experience, the optimization process follows a common cycle:
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
1. [Identify performance Issues](identify.md) - Use Roblox's built-in tools to find performance problems and identify root causes.
|
||||
1. [Improve performance](improve.md) - After you find an issue, see if it's a common one and how you might mitigate it. You might also want to take proactive action.
|
||||
1. [Monitor performance](monitor.md) - Use analytics tools to monitor the impact of your changes and watch for indicators of any performance issues.
|
||||
</figure>
|
||||
<figure>
|
||||
<img alt="A diagram showing the flow from designing, identifying, improving, and monitoring performance." src="../assets/optimization/Perf-Flow.png" width="70%" />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Why performance matters
|
||||
|
||||
To put it simply, performant experiences are more fun. When an experience has a consistent, smooth frame rate, even basic actions like running and jumping feel more precise and enjoyable. Aiming can go from feeling impossible to feeling completely natural. An experience with a small memory footprint and fast load times might always have plenty of other people to play with.
|
||||
|
||||
Performance optimization also lets you expand your ambitions. If your experience runs well, you might be able to make it more expansive or beautiful or add new systems to it.
|
||||
|
||||
From a business perspective, performant experiences often have higher user engagement, better user retention, and make more money.
|
||||
|
||||
## Measure performance
|
||||
|
||||
- **Frame rate** is the number of unique frames that the client presents to the user, measured in frames per second (FPS). The cause of low frame rates is typically expensive computational operations—in other words, overtaxing the CPU, GPU, or both.
|
||||
|
||||
Because the server doesn't render frames, it doesn't _technically_ have a frame rate, but server heartbeat is essentially the same thing: the number of times per second that the server updates its simulation of the world. A low server heartbeat reduces the frame rate for all clients and increases latency.
|
||||
|
||||
On the client, low or inconsistent frame rates reduce the smoothness and responsiveness of the experience. By default, Roblox experiences target 60 FPS, which is one frame every 16.67 milliseconds with proper frame pacing.
|
||||
|
||||
- **Memory usage** is the amount of RAM or swap that your experience uses. Even if an experience has low starting memory usage, memory leaks can cause that amount to increase over time.
|
||||
|
||||
On the server, excessive memory usage can cause crashes, which disconnect all players from the experience.
|
||||
|
||||
Excessive memory usage causes client crashes, too, but it also prevents users on lower-end devices from playing your experience in the first place. Reducing memory usage can greatly expand your addressable audience, especially on mobile.
|
||||
|
||||
- **Load time** is the amount of time it takes for a player to start enjoying your experience.
|
||||
|
||||
Long load times are annoying and hurt user acquisition and retention. Players might decide an experience simply isn't worth the wait. Conversely, quick load times can give your experience a "jump in" feel that makes players more likely to engage with it when they have a spare moment.
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
title: MicroProfiler
|
||||
description: The MicroProfiler is a Studio and client tool for optimizing your experience.
|
||||
---
|
||||
|
||||
The **MicroProfiler** is a performance optimization and troubleshooting tool in Roblox Studio and the Roblox client. It provides detailed, visual timing information for all the [engine tasks](task-scheduler.md) that run during a frame, like animating characters, updating physics, running scripts, and rendering geometry. You can identify which tasks ran during which frame, how long these tasks took to run, and whether any caused you to miss your frame time goals.<br />
|
||||
|
||||
<Grid container spacing={2} style={{ marginBottom: 24, width: '100%' }}>
|
||||
<Grid item xs={6} style={{ padding: 16 }}>
|
||||
<Grid item container wrap="nowrap" direction="column" style={{ gap: 8, flex: 1 }}>
|
||||
|
||||
<div
|
||||
className="container"
|
||||
style={{ position: "relative", paddingBottom: "56.25%", height: 0, marginBottom: 12 }} >
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/XLCZfGEm9tU?si=d992GKSU7oveIrhd"
|
||||
title="YouTube video player"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }} ></iframe>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6} style={{ padding: 16 }}>
|
||||
<Grid item container wrap="nowrap" direction="column" style={{ gap: 8, flex: 1 }}>
|
||||
|
||||
<div
|
||||
className="container"
|
||||
style={{ position: "relative", paddingBottom: "56.25%", height: 0, marginBottom: 12 }} >
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/eWf-6Sh-9Os?si=GZYvQw6ZvQiZF1uv"
|
||||
title="YouTube video player"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }} ></iframe>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
## Frame times
|
||||
|
||||
To use the MicroProfiler, you must understand the relationship between **frame time** and **frame rate**.
|
||||
|
||||
- Frame time is how long a frame took to render on screen, generally measured in milliseconds.
|
||||
- Frame rate is the number of frames rendered on screen, generally measured in frames per second (FPS).
|
||||
|
||||
Lower frame times mean higher frame rate. Divide 1,000 milliseconds (1 second) by frame time to get frame rate.
|
||||
|
||||
Average frame time | Frames per second
|
||||
:--- | :---
|
||||
33.33 ms | 30 FPS
|
||||
16.67 ms | 60 FPS
|
||||
8.33 ms | 120 FPS
|
||||
4.17 ms | 240 FPS
|
||||
|
||||
For smooth gameplay, however, it's not enough to have a high frame rate. You need **consistent frame times**. For example, if 59 frames arrive in 10 milliseconds and one frame in 410 milliseconds, players perceive a huge, jarring stutter, even though the experience is running at 60 FPS. If all frames take roughly the same amount of time to render, your experience will feel perceptibly smoother due to that consistency, especially at lower frame rates.
|
||||
|
||||
The MicroProfiler focuses **entirely on frame time**. Its purpose is to help you identify frame time spikes and what caused them.
|
||||
|
||||
## Open the MicroProfiler
|
||||
|
||||
Opening the MicroProfiler varies by platform:
|
||||
|
||||
- On the mobile client, open the **Settings** menu and change the MicroProfiler to **On**. Then from a development machine **on the same network**, use your web browser to navigate to the provided IP address and port.
|
||||
|
||||
<img alt="The MicroProfiler UI on mobile." src="../../assets/optimization/microprofiler/micro-mobile.png" width="700px" />
|
||||
|
||||
This screenshot shows an IP address of `172.18.56.105` and a port of `1338`, so you navigate to `172.18.56.105:1338`. Depending on your device's network configuration, you might see multiple IP addresses. If one doesn't work, try the next.
|
||||
|
||||
<Alert severity="info">
|
||||
By default, the MicroProfiler web UI shows the 30 most recent frames from your mobile device. For a larger set of frames, add a slash and a number to the URL, such as `172.18.56.105:1338/90`. To get a fresh set of frames, use the **Re-capture** button.
|
||||
</Alert>
|
||||
|
||||
- On the desktop client, press <kbd>Ctrl</kbd><kbd>F6</kbd> (<kbd>⌘</kbd><kbd>F6</kbd>).
|
||||
- In Studio, press <kbd>Ctrl</kbd><kbd>F6</kbd> (<kbd>⌘</kbd><kbd>F6</kbd>).
|
||||
|
||||
Generally, the mobile client is the best place to profile your experience. Most players on Roblox use phones and tablets, and these devices have severe thermal and power constraints that limit their performance. If your experience runs well on a midrange Android tablet, it almost certainly performs much, much better on a gaming desktop.
|
||||
|
||||
<Alert severity="success">
|
||||
Powerful devices like gaming desktops can actually obscure performance problems, especially if you have a frame rate cap in place. For example, if you cap FPS to 60 on the client, you might not notice the difference between a frame time of 4 milliseconds and 16 milliseconds, even though the latter is four times as long, because they all arrive in under 16.67 milliseconds. On a mobile device that is struggling to stay at 30 FPS, a frame that takes four times as long (133 ms) is hard to miss.
|
||||
</Alert>
|
||||
|
||||
## MicroProfiler basics
|
||||
|
||||
The MicroProfiler has two key components: the **frame time bar graph** and the **detailed task timeline**.
|
||||
|
||||
- The frame time graph runs along the top of the interface and shows how long each frame took to render. Taller bars mean longer frame times.
|
||||
- The timeline shows all of the individual tasks that ran during the frame. Wider bars represent tasks that took longer to run.
|
||||
|
||||
A typical workflow in the MicroProfiler is to use the frame time graph to identify a spike (a taller bar) and then use the timeline to identify which tasks caused the spike. Then you can check the [Tag reference](tag-table.md) for information on the task and how to improve its performance.
|
||||
|
||||
<img alt="Example of a frame time spike." src="../../assets/optimization/microprofiler/micro-spike.png" width="600px" />
|
||||
|
||||
### Frame time graph
|
||||
|
||||
The height of each bar indicates the number of milliseconds that it took to complete the frame. More recent frames are on the right. Hover over a frame for some basic information around CPU and GPU usage.
|
||||
|
||||
<img alt="The Microprofiler frame graph, showing blue frames and detailed frame information." src="../../assets/optimization/microprofiler/micro-frames.png" width="500px" />
|
||||
|
||||
- Orange bars are frames where the **Jobs Wall Time** exceeds the **Render Wall Time**. In these frames, at least one of the [worker threads](#threads), which do things like run scripts, calculate physics, and play animations, took longer to run than the [main render thread](#threads).
|
||||
|
||||
If the experience is not reaching your frame time goals and has a large number of orange frames, common causes are scripts, physics, and animations. See [Improve performance](../../performance-optimization/improve.md#script-computation).
|
||||
|
||||
- Blue bars are frames where the **Render Wall Time** exceeds the **Jobs Wall Time**. In these frames, the main render thread took more time than any of the worker threads.
|
||||
|
||||
If the experience is not reaching your frame time goals and has a large number of blue frames, that indicates a rendering bottleneck. Common causes are excessive object density, object movement, and lighting. See [Improve performance](../../performance-optimization/improve.md#rendering).
|
||||
|
||||
- Red bars are frames where two conditions are true:
|
||||
|
||||
- **Render Wall Time** exceeds **Jobs Wall Time**
|
||||
- **GPU Wait Time** is greater than 2.5 milliseconds
|
||||
|
||||
Red bars are less common than orange and blue and often the result of excessive object complexity, texture size, and visual effects. Optimization is similar to blue bars. See [Improve performance](../../performance-optimization/improve.md#rendering).
|
||||
|
||||
Tiny tasks at the end of a frame can sometimes throw off the **Jobs Wall Time** and **Render Wall Time**, which is a good reason to focus more on frame time than frame color. There's no "good" color to strive for. A mixture of orange, blue, and red isn't problematic as long as you're reaching your frame time goals. If you **aren't** reaching your frame time goals, the colors can indicate where to optimize.
|
||||
|
||||
### Timeline
|
||||
|
||||
- Left-click and drag to pan the timeline. On a machine with many CPU cores, you might need to pan up or down quite a bit to find the [thread](#threads) performing a particular task.
|
||||
|
||||
<img alt="The lefthand side of the Microprofiler detailed view, with rows for threads." src="../../assets/optimization/microprofiler/micro-panning.png" width="700px" />
|
||||
|
||||
- Scrolling zooms the timeline in and out. Combined with the millisecond labels at the top of the timeline, you can get a sense of how long a task took in an absolute sense, but also how long it took relative to other tasks.
|
||||
|
||||
<img alt="The Microprofiler detailed view with numerous horizontal labels." src="../../assets/optimization/microprofiler/micro-timeline.png" width="500px" />
|
||||
|
||||
- The green overlay on the bar graph indicates the number of frames currently visible on the timeline—your zoom level. Left-click on a frame bar to jump to that position on the timeline.
|
||||
|
||||
You might have to adjust your zoom level, but hovering over a bar highlights the frame on the timeline. Light grey lines delineate frames.
|
||||
|
||||
<img alt="The MicroProfiler bar graph with green overlay to show zoom level." src="../../assets/optimization/microprofiler/micro-detailed.png" width="600px" />
|
||||
|
||||
- Labels that appear directly below another label indicate tasks that are performed as part of the higher-level task.
|
||||
|
||||
Rather than the parent task, you typically want to troubleshoot the worst-performing child tasks; a parent task can't be shorter than the sum of its child tasks.
|
||||
|
||||
- Right-click a label to zoom the timeline to exactly the duration of that task.
|
||||
|
||||
- If the amount of information is overwhelming, use the **Groups** or **Threads** menu to filter the timeline. For example, you might only want to see **Render** tasks in the main thread.
|
||||
|
||||
<img alt="The on-hover view for a label, with Group highlighted." src="../../assets/optimization/microprofiler/micro-group.png" width="500px" />
|
||||
|
||||
- Use <kbd>Ctrl</kbd><kbd>F</kbd> (<kbd>⌘</kbd><kbd>F</kbd>) to jump to the occurrence of a task that takes up the most time in the dump. For example, `computeLightingPerform` runs in every client frame. If you search for it in a dump with 128 frames, you can jump to the frame where it took the longest to complete.
|
||||
|
||||
## Threads
|
||||
|
||||
Like many programs, Roblox uses multiple threads to perform sequences of tasks at the same time. The MicroProfiler displays the thread name on the lefthand side of the timeline.
|
||||
|
||||
<img alt="The lefthand side of the Microprofiler detailed view, with rows for threads." src="../../assets/optimization/microprofiler/micro-panning.png" width="600px" />
|
||||
|
||||
There are three important thread types:
|
||||
|
||||
- **Main** ("RBX Main"): Handles CPU-based rendering tasks. Processes input, `Class.Humanoid|Humanoids`, animations/tweening, physics ownership, sound, and waiting script resumes. Also updates Studio interfaces and coordinates the other threads.
|
||||
|
||||
- **Worker** ("RBX Worker"): Helps the main thread with networking, physics, and pathfinding. Due to the number of cores in modern CPUs, you likely have many worker threads, most of which are in a sleep state at any given time.
|
||||
|
||||
- **Render** ("GPU"): Follows a "prepare, perform, present" logic. Communicates with the graphics processing unit (GPU) of the device.
|
||||
|
||||
- Prepare: Information from the main thread is used to update rendering models.
|
||||
- Perform: Issue rendering commands, including 2D interfaces.
|
||||
- Present: Synchronizes with the GPU.
|
||||
|
||||
## Custom script profiles
|
||||
|
||||
If your scripts are running complicated tasks, you can profile critical portions of the code to ensure that they're not taking too long. Wrap code with `Library.debug.profilebegin()` and `Library.debug.profileend()` to time everything done between those function calls and create a label on the MicroProfiler timeline.
|
||||
|
||||
```lua
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local function onPreSimulation()
|
||||
debug.profilebegin("Hard Work")
|
||||
-- Example hard work: swap two variables 200,000 times
|
||||
local a, b = 0, 1
|
||||
for _ = 1, 200000 do
|
||||
a, b = b, a
|
||||
end
|
||||
debug.profileend()
|
||||
end
|
||||
RunService.PreSimulation:Connect(onPreSimulation)
|
||||
```
|
||||
|
||||
<img alt="Many labels on the MicroProfiler, with a custom label taking up the majority of the processing time." src="../../assets/optimization/microprofiler/custom-profile.png" width="500px" />
|
||||
|
||||
From its duration on the timeline, you can tell that the function is using a lot of processing time compared to other tasks.
|
||||
|
||||
## Save frame data
|
||||
|
||||
If you want to save a set of frame data for later review (or to share with someone else), use the **Save to file** button. The MicroProfiler saves frame data to a standalone HTML file named `microprofile-<date>-<time>.html`.
|
||||
|
||||
<img alt="The save to file button in the web UI." src="../../assets/optimization/microprofiler/micro-save-to-file.png" width="700px" />
|
||||
|
||||
On the desktop client or in Studio, use the **Dump** menu. In both cases, the MicroProfiler automatically saves the file to the Roblox logs directory:
|
||||
|
||||
- On Windows, check `%LOCALAPPDATA%\Roblox\logs`.
|
||||
|
||||
If you use the Universal Windows Platform (UWP) app, check `\Users\<username>\AppData\Local\Packages\ROBLOXCORPORATION.ROBLOX_<hash>\LocalState\logs\`.
|
||||
|
||||
- On macOS, check `~/Library/Logs/Roblox`.
|
||||
|
||||
<Alert severity="info">
|
||||
Dumps only contain data for the selected number of frames, **not** the entire duration that the experience has been running. The exception is [counters mode](modes.md#counters-mode), which includes data from when Studio or the client started running to the time of the dump.
|
||||
</Alert>
|
||||
|
||||
## Profile the server
|
||||
|
||||
In addition to profiling the client, you can capture brief dumps of server activity:
|
||||
|
||||
1. On the desktop client, join an experience that you have edit permissions for.
|
||||
1. Open the Developer Console with <kbd>Ctrl</kbd><kbd>F9</kbd> (<kbd>⌘</kbd><kbd>F9</kbd>).
|
||||
1. In the dropdown menu, select **MicroProfiler**.
|
||||
1. In the **Server** tab, specify the number of frames to capture (maximum 60) and the number of seconds to delay before starting the capture (maximum 4).
|
||||
1. Click **Begin server recording**.
|
||||
|
||||
<img alt="The Developer Console MicroProfiler screen." src="../../assets/optimization/microprofiler/micro-server.png" />
|
||||
|
||||
After a few seconds, the Developer Console provides the path to the file, which is the same path as a [standard dump](#save-frame-data).
|
||||
|
||||
## Web-only features
|
||||
|
||||
The MicroProfiler has two user interfaces: the web UI (mobile and dumps) and the desktop UI (client and Studio). We recommend using the web UI whenever possible. In general, the two work similarly, but the web UI has a few extra features:
|
||||
|
||||
- Use the **X-Ray** menu to enable or disable color coding for memory allocation.
|
||||
|
||||
<img alt="The MicroProfiler web view with x-ray enabled." src="../../assets/optimization/microprofiler/micro-xray.png" width="600px" />
|
||||
|
||||
- Lighter frames within the main bar graph indicate higher memory allocation.
|
||||
- Lighter portions of the preview bar and lighter labels on the timeline indicate portions of the frame with higher memory allocation.
|
||||
- In X-ray mode, press <kbd>C</kbd> to show the total size of the memory allocations rather than the number of allocations.
|
||||
|
||||
- Use the **Export** menu to export a CPU or memory flame graph, a specialized visualization that aggregates all of the call stacks included in the dump, maintains the parent-child hierarchy, and sizes them based on duration. Flame graphs are especially useful for identifying tasks that don't cause you to miss your frame time goals (and are therefore hard to notice), but run so often that their processing time becomes significant.
|
||||
|
||||
<img alt="The MicroProfiler flame graph in the web UI." src="../../assets/optimization/microprofiler/micro-flame.png" />
|
||||
|
||||
You can also create flame graphs in Studio, although only for scripts (execution time and memory allocations). Compared to the web-based flame graphs, the ones in Studio are top-down rather than bottom-up and support dramatically longer capture times.
|
||||
|
||||
<img alt="The MicroProfiler flame graph in Studio." src="../../assets/optimization/microprofiler/micro-studio-flame.png" />
|
||||
|
||||
- Drag and drop a second dump file into the web UI to generate a diff flame graph, which can help you identify improvements or regressions to your experience's performance over time. Click **Combine & Compare** to export a new HTML file.
|
||||
|
||||
<img alt="The MicroProfiler diff flame graph builder showing the left and right sides." src="../../assets/optimization/microprofiler/micro-flame-diff.png" />
|
||||
|
||||
<Alert severity="success">
|
||||
You can combine several dump files—for example, four on each side, from before and after a major update—to get a more aggregated comparison. Comparing data between different places can be useful, too, but don't **combine** data from different places!
|
||||
</Alert>
|
||||
|
||||
The diff flame graph highlights the dump (left or right) that consumes more CPU or memory, with brighter colors indicating a larger difference.
|
||||
|
||||
<img alt="The MicroProfiler diff flame graph." src="../../assets/optimization/microprofiler/micro-flame-diff2.png" />
|
||||
|
||||
## Desktop-only features
|
||||
|
||||
On the desktop timeline, left-click a label to add it to a line graph in the bottom-right. The graph shows the time the task takes each frame. Using this graph, you can test the performance of only certain tasks in your experience. When you're done, right-click the graph to hide it.
|
||||
|
||||
<img alt="The graph showing how long tasks take per frame." src="../../assets/optimization/microprofiler/micro-graph.png" width="300px" />
|
||||
|
||||
This graph is synchronized with the main profiler bars. The currently selected frame range is highlighted in green on the graph. You can also click anywhere on this graph to re-center the main profiler view to that specific frame, which is helpful for finding spikes in the detailed view.
|
||||
|
||||
<Alert severity="info">
|
||||
Use the **Options** menu to change the reference line on the graph to a different number of milliseconds.
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: MicroProfiler modes
|
||||
description: The MicroProfiler includes several different modes, all of which .
|
||||
---
|
||||
|
||||
The MicroProfiler includes several modes, which can help you visualize your experience's performance characteristics in different ways. After opening the MicroProfiler, use the **Modes** menu to switch between them.
|
||||
|
||||
**Frame mode** and **detailed mode** are covered under [MicroProfiler basics](index.md#microprofiler-basics).
|
||||
|
||||
## Timers mode
|
||||
|
||||
**Timers mode** is an alternative way of visualizing the data in the detailed view: as a list of labels with processing times and call counts. Horizontal bar graphs in some columns help you spot the busiest tasks.
|
||||
|
||||
<img alt="Timers mode." src="../../assets/optimization/microprofiler/micro-timers-mode.png" width="600px" />
|
||||
|
||||
Controls are similar to detailed view:
|
||||
|
||||
- Left-click and hold to pan up and down.
|
||||
- Left-click a label to add it to the line graph in the lower-right.
|
||||
- Use the **Groups** menu to filter the list.
|
||||
- Use the **Timers** menu to customize the columns.
|
||||
|
||||
<Alert severity="info">
|
||||
All of the information in this view is visible when you hover over a label in the detailed view.
|
||||
</Alert>
|
||||
|
||||
## Counters mode
|
||||
|
||||
Counters mode is a lengthy list of categories and statistics, including instance count and memory usage (in bytes) for the various tasks.
|
||||
|
||||
<img alt="Counters mode with a single graph." src="../../assets/optimization/microprofiler/micro-counters-mode.png" width="400px" />
|
||||
|
||||
- Just like the detailed view, left-click and hold to pan up and down.
|
||||
- Left-click in the **Graph** column to add a small usage graph with minimum and maximum values. Left-click the graph again to expand it.
|
||||
- Right-click a graph to close it.
|
||||
- You can't filter this view, but you can left-click on a category (for example, `memory`) to collapse it.
|
||||
|
||||
While counters mode can be useful, the [Developer Console](../../studio/optimization/memory-usage.md) is the best way to [identify memory issues](../../performance-optimization/identify.md#client-memory). You might also find the [X-ray view](index.md#web-only-features) helpful for identifying when problematic memory allocation occurs.
|
||||
|
||||
## Groups mode
|
||||
|
||||
Only available in the web UI. Similar to timers mode, groups mode provides average, max, and total task times for each group, broken down by thread, to help you identify how broad categories of tasks are impacting your frame times.
|
||||
|
||||
<img alt="MicroProfiler web UI showing groups mode." src="../../assets/optimization/microprofiler/micro-groups-mode.png" width="600px" />
|
||||
|
||||
## Threads mode
|
||||
|
||||
Only available in the web UI. Similar to timers mode, threads mode provides average and total task times for each thread, broken down by group, to help you identify how your experience is distributing tasks across threads.
|
||||
|
||||
<img alt="MicroProfiler web UI showing threads mode." src="../../assets/optimization/microprofiler/micro-threads-mode.png" width="600px" />
|
||||
|
||||
## Hidden mode
|
||||
|
||||
Only available in the desktop UI. Hidden mode keeps the MicroProfiler menu open, but hides the bar graph. It's useful for reducing visual clutter, [saving frame data](index.md#save-frame-data), and pausing and unpausing while you observe the line graph.
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: Network usage
|
||||
description: The MicroProfiler provides detailed information on network usage within your experience.
|
||||
---
|
||||
|
||||
The MicroProfiler also lets you capture and view network traffic, which can help you identify causes of high latency and loading problems in your experiences.
|
||||
|
||||
Network data is available **only in frame dumps from the desktop client or Studio**, not in the real-time overlay. If you encounter lag or other issues that you suspect are network-related, that's a great time to [save MicroProfiler data](index.md#save-frame-data).
|
||||
|
||||
## Capture network data
|
||||
|
||||
1. Open the MicroProfiler with <kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F6</kbd> or <kbd>⌘</kbd><kbd>⌥</kbd><kbd>F6</kbd>.
|
||||
|
||||
1. In the **Network** menu, choose **High**, **Low**, or **Off**.
|
||||
|
||||
- **High** includes additional information on the items within a batch data packet, the task that deserialized the packet, and the asset ID for any assets in the request or response.
|
||||
|
||||
- **Low** captures less data and is not as computationally intensive. Depending on your computer, selecting it can help your dumps contain a larger number of frames. This is the default level and the one the MicroProfiler uses when you [profile a mobile device](index.md#profile-mobile-devices).
|
||||
|
||||
- **Off** means that network data is not included in frame dumps.
|
||||
|
||||
1. [Save MicroProfiler frame data](index.md#save-frame-data) and open the generated HTML file.
|
||||
|
||||
## Basic navigation
|
||||
|
||||
After you open the MicroProfiler dump in a web browser, select **X-Ray** / **Network**. Two more rows appear below the frame time graph:
|
||||
|
||||
- The top row is received traffic (downloads).
|
||||
- The bottom row is sent traffic (uploads).
|
||||
|
||||
<img alt="Overview of the network graphs." src="../../assets/optimization/microprofiler/micro-network.png" width="750px" />
|
||||
|
||||
Because network traffic can vary dramatically between frames, you might see a couple spikes and seemingly very little else. In this case, select **Options** / **Log Scale Graph** to change the scaling of the bars such that they scale logarithmically rather than linearly, which makes small bars more prominent.
|
||||
|
||||
The stacked bars are colored not by severity, but by type of traffic: physics in blue, data in green, and assets in red. Hover over any bar to get the amount of received or sent traffic for that frame. Right-click to show the **Network events** window, which shows much more detailed information.
|
||||
|
||||
## Network events window
|
||||
|
||||
The **Network events** window shows individual events within the scope of the frame. Click on any event for more detailed information.
|
||||
|
||||
<img alt="Overview of the network graphs." src="../../assets/optimization/microprofiler/micro-network-events.png" width="450px" />
|
||||
|
||||
Available information depends on the type of packet and verbosity level:
|
||||
|
||||
- **Engine network batch data** includes traffic due to streaming, instance property updates, and events. If you chose high verbosity, the batch data expands to show individual items. At low verbosity, you only get the summary.
|
||||
|
||||
- At high verbosity, click an Engine network packet to zoom in on the task that deserialized the packet.
|
||||
- Regardless of verbosity level, click any other Engine network event to show information such as size, direction, and type.
|
||||
|
||||
- Compared to Engine network events, **HTTP asset delivery** shows some additional information, including batch size (if the request was batched) and the number of queued HTTP requests (if applicable). If you chose high verbosity, it also includes asset IDs for any assets in the request.
|
||||
|
||||
- Click the asset ID to zoom in on the task where the next step in fetching that asset took place.
|
||||
- Click the **+** to view the asset on the Creator Store.
|
||||
|
||||
<img alt="Overview of the network graphs." src="../../assets/optimization/microprofiler/micro-network-asset.png" width="450px" />
|
||||
|
||||
## Additional graphs
|
||||
|
||||
Depending on the data included in the frame dump, you can view additional graphs below the standard download and upload graphs:
|
||||
|
||||
- Asset fetching
|
||||
- Data
|
||||
- Large replicator data (async and chunked data)
|
||||
- Physics
|
||||
- Terrain
|
||||
|
||||
Click any category in the legend to add these graphs, which provide dedicated views into each packet type. Each graph defaults to showing received traffic (Rx), but you can use the label in the upper-left of any of the three graphs to swap to sent traffic (Tx).
|
||||
|
||||
<img alt="Overview of the network graphs." src="../../assets/optimization/microprofiler/micro-network-extra.png" width="750px" />
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
---
|
||||
title: Tag reference
|
||||
description: A list of tags for the MicroProfiler.
|
||||
---
|
||||
|
||||
The following is a list of common tags in the [MicroProfiler](./index.md), grouped by category. Tags are also interchangeably called tasks, timers, scopes, processes, and labels. Whatever the name, they represent a unit of work.
|
||||
|
||||
Understanding these tags can help you identify problematic code in your experience. The tables contain tag label, descriptions, and advice for improving performance and optimizing your experience.
|
||||
|
||||
## Sleep
|
||||
|
||||
When threads aren't actively performing tasks, they enter a sleep state, with tags to indicate how long the thread was sleeping. At any given time, it's extremely common for most worker threads to be in a sleep state.
|
||||
|
||||
## Navigation
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>rasterizeTile</td>
|
||||
<td>Updates navigation tiles needed for a pathfinding request, usually followed by **computePath** which requires those tiles be up-to-date. Follows **NavigationJob/preprocess** on the main thread.</td>
|
||||
<td>Reduce the number of pathfinding tile invalidations, as this causes those paths to need recomputing. This is caused by non-navigable parts moving.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>computePath</td>
|
||||
<td>
|
||||
Calculates paths, typically after **rasterizeTile**.
|
||||
</td>
|
||||
<td>Reduce the number and world extents of `Class.Path:ComputeAsync()` calls. Try reusing paths for multiple agents if they are expected to start/end from approximately similar locations.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>preprocess/getPrimitivesOverlapping</td>
|
||||
<td>Collects the world geometry for each pathfinding tile and schedules the rasterization tasks to be executed by the pathfinding thread.</td>
|
||||
<td>Reduce the part count.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Animation and humanoids
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Simulation/gameStepped</td>
|
||||
<td>Processing of experience-specific objects such as `Class.Humanoid|Humanoids`, `Class.Animation|Animations` and heartbeat callbacks.</td>
|
||||
<td>See **gameStepped** labels below.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/gameStepped/stepHumanoid</td>
|
||||
<td>`Class.Humanoid` state changes and movement.</td>
|
||||
<td>Reduce the amount of `Class.Humanoid|Humanoids` or humanoids with `Class.Humanoid.EvaluateStateMachine` enabled. Disable `Class.Humanoid` states on NPCs that don't need them, such as `Enum.HumanoidStateType|Climbing` or `Enum.HumanoidStateType|Swimming`. Reduce callbacks to `Class.Humanoid.StateChanged` or state changes such as `Class.Humanoid.Running|Running` or `Class.Humanoid.Died|Died`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/gameStepped/stepAnimation</td>
|
||||
<td>`Class.Animator|Animators` will step forward in currently playing animations.</td>
|
||||
<td>Reduce the amount of `Class.Animator|Animators` or animated joints to lower the workload of this step. Reduce number of callbacks to animation events such as `Class.AnimationTrack.KeyframeReached` or `Class.AnimationTrack.Ended`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/gameStepped/RunService.Stepped</td>
|
||||
<td>Runs functions connected to the `Class.RunService.Stepped` event.</td>
|
||||
<td>Reduce the amount or workload of functions connected to this event. Consider delaying or replacing expensive calculations or spreading computation across multiple frames.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Audio
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Sound</td>
|
||||
<td>Processes acoustic simulation and updates sounds in active playback.</td>
|
||||
<td>See **Sound** labels below.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sound/stepInstances</td>
|
||||
<td>Updates the volume of active sounds in the workspace.</td>
|
||||
<td>Reduce the amount of sounds in active playback.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Networking
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Net PacketReceive</td>
|
||||
<td>Receives network packets. If many objects or events are being replicated, this step takes longer. </td>
|
||||
<td>Replicate fewer objects or events.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Replicator/ProcessPackets</td>
|
||||
<td>Processes contents of network packets, such as motion, event invocations and property changes. </td>
|
||||
<td>Reduce the number or size of objects being replicated, or do this in incremental steps. May increase if map size increases, as larger maps tend to have more overall activity.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allocate Bandwidth and Run Senders/Dispatch Physics Senders and TouchSenders</td>
|
||||
<td>Sends data about activity in the experience.</td>
|
||||
<td>Reduce the amount of moving objects and/or touches. See following sections.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allocate Bandwidth and Run Senders/Dispatch StreamJob</td>
|
||||
<td>Sends corresponding specific regions to specific clients in the streaming feature.</td>
|
||||
<td>Reduce the minimum and target streaming radii.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allocate Bandwidth and Run Senders/Dispatch Data Senders</td>
|
||||
<td>Sends property changes, remote event invocations, `Class.Humanoid` state changes, animation state changes, and replication of new instances.</td>
|
||||
<td>Reduce the number of replicated changes to the data model.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Replicator SendCluster</td>
|
||||
<td>Sends [Terrain](../../parts/terrain.md) data to clients.</td>
|
||||
<td>Reduce the amount or size of terrain changes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ModelCompleteness Mega Job</td>
|
||||
<td>Server only: completeness is an internal concept. When models are completely sent, model completeness events are sent.</td>
|
||||
<td>Add or remove fewer instances.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>deserializePacket</td>
|
||||
<td>Low-level network packet processing. Prepares for **Replicator/ProcessPackets**.</td>
|
||||
<td>Send fewer or smaller updates.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Rendering
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Prepare</td>
|
||||
<td>Information is gathered from the main thread and updates various data structures used for rendering.</td>
|
||||
<td>See **Prepare** labels below.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/Pass3dAdorn</td>
|
||||
<td>Handles rendering of various object adorns, such as text labels above objects. For `Class.Humanoid` labels with occlusion, this step includes raycasting to determine if such objects are visible. This includes non-transparent parts to facilitate debug visualizations.</td>
|
||||
<td>Reduce the number of visible adorned objects, such as `Class.BillboardGui|BillboardGuis`, `Class.Humanoid` name/health labels, etc. Reduce the number of visible parts.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/Pass2d</td>
|
||||
<td>Readies 2D UI rendering (both player and Roblox UI).</td>
|
||||
<td>Reduce the amount or complexity of UI elements.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/UpdatePrepare/updateInvalidParts</td>
|
||||
<td>Updates parts that had some property changed or added.</td>
|
||||
<td>Reduce the amount of properties changes on the world. If a script updates a large set of object properties, break it down across frames.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/UpdatePrepare/updateInvalidatedFastClusters</td>
|
||||
<td>Prepares "FastCluster" geometries used to render `Class.Humanoid|Humanoids` and [skinned](../../art/modeling/rigging.md) `Class.MeshPart|MeshParts`. Labels specify the number of parts, vertices, and size of vertices.</td>
|
||||
<td>Reduce visual changes to models with `Class.Humanoid|Humanoids` or skinned `Class.MeshPart|MeshParts`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/UpdatePrepare/updateDynamicParts</td>
|
||||
<td>Prepares `Class.Beam|Beams`, `Class.ParticleEmitter|ParticleEmitters`, and `Class.Humanoid|Humanoids` for rendering.</td>
|
||||
<td>Reduce the number of visible `Class.Beam|Beams`, `Class.ParticleEmitter|ParticleEmitters`, and `Class.Humanoid|Humanoids`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prepare/UpdatePrepare/updateInstancedClusters</td>
|
||||
<td>Updates geometry that uses instanced rendering such as parts. Labels **Clusters** and **Instances** indicate the number updated.</td>
|
||||
<td>Reduce work that implicitly updates the bounding box of the part, such as `Class.BasePart.CFrame`, `Class.BasePart.Size`, or `Class.Motor6D.Transform`. Creative property updates such as `Class.Bone.Transform` can help.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform</td>
|
||||
<td>When actual rendering commands are created and issued.</td>
|
||||
<td>See **Perform** labels below.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/fillGuiVertices</td>
|
||||
<td>Fills buffers with UI vertices to prepare for rendering. **gui count** label indicates the amount of `Class.LayerCollector|LayerCollectors` visible in the frame.</td>
|
||||
<td>If the cost is high, reduce the amount, density, and space taken by UI elements. If there are too many **Process GuiEffect** labels, consider reducing the use of `Class.UIGradient` and `Class.UICorner` on text labels.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/queryFrustumOrdered</td>
|
||||
<td>Applies frustum culling so that objects not visible are not rendered.</td>
|
||||
<td>If there is a high cost that means there are a lot of elements. Perhaps use some larger meshes where a single mesh has more details as opposed to many small individual pieces.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/computeLightingPerform</td>
|
||||
<td>Computation of lighting near the camera.</td>
|
||||
<td>Manipulate the number of light sources or move the camera less to reduce the time it takes to recalculate lighting.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/computeLightingPerform/LightGridCPU</td>
|
||||
<td>Updates the voxel lighting, which is used at lower quality levels.</td>
|
||||
<td>If updating chunk occupancy takes too long, consider using lower resolution geometry, reducing the number of parts, or anchoring parts. If the other labels take too long, consider reducing the number of lights and using non-shadow casting geometry for objects that move and invalidate the occupancy.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/computeLightingPerform/ShadowMapSystem</td>
|
||||
<td>Updates shadow maps. Not performed at quality levels below 4.</td>
|
||||
<td>Reduce the number of lights. You can also use `Class.Light.Shadows` and `Class.BasePart.CastShadow` to disable shadow casting on less important instances. See [Improve performance](../../performance-optimization/improve.md#mitigation-4).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/Glow, ColorCorrection, MSAA, SSAO, and SSAOApply</td>
|
||||
<td>Post-processing rendering.</td>
|
||||
<td>Reduce the number of post-processing effects. Usually this is not significant.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/UI</td>
|
||||
<td>UI rendering. In **Id_Screen**, there is a label with the number of batches, materials and triangles used.</td>
|
||||
<td>Reduce the number of visible UI elements. Using `Class.CanvasGroup|CanvasGroups` can help at the expense of increased memory use.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Scene/UpdateView/updateParticles, updateParticleBoundings</td>
|
||||
<td>Update particle position and bounds.</td>
|
||||
<td>Reduce the number of `Class.ParticleEmitter|ParticleEmitters`, emission rates, lifetimes, etc. Limit the movement of emitters.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scene/Id_Opaque, RenderView/Id_Opaque</td>
|
||||
<td>Parts in the scene with an overall transparency of `0.01` or lower.</td>
|
||||
<td>Reduce the use and density of parts.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scene/Id_Transparent, RenderView/Id_Transparent</td>
|
||||
<td>Parts in the scene with an overall transparency between `0.01` and `1`.</td>
|
||||
<td>Reduce the use of partial transparency.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scene/Id_Decals, RenderView/Id_Decals</td>
|
||||
<td>Decals in the scene.</td>
|
||||
<td>Reduce the use of decals on complex meshes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scene/Shadows</td>
|
||||
<td>Shadow recalculation in the scene, usually performed on dynamic scenes and typical gameplay. Parts regardless of transparency cast shadows under the assumption that they contain decals. Not performed at quality levels below 4.</td>
|
||||
<td>If this step takes too long, consider disabling `Class.BasePart.CastShadow` for complex meshes, parts with high partial transparency, and less important instances. Fully transparent parts that don't have decals or need decal shadows should have `Class.BasePart.CastShadow` disabled. See [Improve performance](../../performance-optimization/improve.md#mitigation-4).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Present</td>
|
||||
<td>Forwards to the GPU thread to perform rendering commands.</td>
|
||||
<td>Reduce scene complexity in general. If this step is taking a long time, you might be limited by the GPU.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Perform/Present/waitUntilCompleted</td>
|
||||
<td>Waits for the GPU to finish rendering the previous frame.</td>
|
||||
<td>If this is generally happening a lot then the amount of things rendered is too high. The Frame Rate Manager helps with balancing this, but if it remains high, try following performance advice from the individual **Scene** tags.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LoadImage</td>
|
||||
<td>Processes images into a format the engine can use.</td>
|
||||
<td>Reduce the use of large images.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Scripts
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Render/PreRender/fireBindToRenderSteppedCallbacks</td>
|
||||
<td>Running functions bound to render step via `Class.RunService:BindToRenderStep()`.</td>
|
||||
<td>Ensure functions connected to this event do as little work as possible.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Render/PreRender/RunService.RenderStepped</td>
|
||||
<td>Runs functions connected to the `Class.RunService.RenderStepped` event.</td>
|
||||
<td>Ensure functions connected to this event do as little work as possible.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WaitingHybridScriptJob</td>
|
||||
<td>Resumes scripts waiting using `Class.Instance:WaitForChild()` or `Globals.Roblox.wait()`. Usually performed 30 times per second. This step has an execution time budget to run waiting scripts.</td>
|
||||
<td>If you have too many waiting scripts or scripts with a long runtime before yielding, this step is throttled and waits longer before it can run again. Reduce the amount of bound functions or long computations in this step.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GC</td>
|
||||
<td>Luau's garbage collection cycle.</td>
|
||||
<td>Pool tables and other collectable objects or try to reduce creating temporary tables.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heartbeat/RunService.Heartbeat</td>
|
||||
<td>Runs functions connected to the `Class.RunService.Heartbeat` event.</td>
|
||||
<td>Reduce the amount or workload of functions connected to this event. Consider delaying or replacing expensive calculations or spreading computation across multiple frames.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Simulation
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Distributed Physics Ownership</td>
|
||||
<td>Determines whether the server or a client has authority over certain instances such as parts.</td>
|
||||
<td>Reduce the amount of parts that frequently switch network ownership, especially those with common interaction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/assemble</td>
|
||||
<td>Updates a tree of connected objects (assemblies) used by the physics engine.</td>
|
||||
<td>Reduce the amount of joints being created or destroyed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/physicsSteppedTotal/physicsStepped</td>
|
||||
<td>Runs the physics simulation.</td>
|
||||
<td>Reduce the amount and complexity of physically simulated bodies.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/physicsSteppedTotal/physicsStepped/SpatialFilter/filterStep</td>
|
||||
<td>Updates simulation islands, arranging parts according to network ownership, local simulation. Islands are non-interacting groups of parts which can be simulated independently.</td>
|
||||
<td>Avoid setting network ownership frequently. Keep groups of parts far enough away from each other so they can be simulated separately.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/physicsSteppedTotal/physicsStepped/worldStep/stepContacts</td>
|
||||
<td>Updates contacts between objects.</td>
|
||||
<td>Reduce the amount of bodies colliding at once, or use simpler collision boxes. Cubes are better than complex meshes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/physicsSteppedTotal/physicsStepped/worldStep/stepWorld **OR** stepWorldThrottled</td>
|
||||
<td>Solves physics equations relating to connectors, buoyancy and `Class.Humanoid|Humanoids`. When the engine is overloaded and unable to simulate everything in real time, some steps may be throttled (**stepWorldThrottled**) and only "real-time assemblies" such as `Class.Humanoid|Humanoids` are simulated.</td>
|
||||
<td>Depends on where the time is going based on the following three phases: **stepContacts**: narrow phase collision detection geometry tests. **Solver step**: integrate time and resolve collisions and other constraints **updateBroadphase**: update positions of assemblies in collision detection system and find possibly colliding narrow phase pairs.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>notifyMovingAssemblies</td>
|
||||
<td>Helps track how long primitives have been sleeping.</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/physicsSteppedTotal/physicsStepped/interpolateNetworkedAssemblies</td>
|
||||
<td>Interpolates assemblies not controlled by the current player.</td>
|
||||
<td>Set the network owner of parts to the current player to reduce this; although this will usually cause more physics work to be done elsewhere.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Simulation/handleFallenParts</td>
|
||||
<td>Removes parts that have fallen below `Class.Workspace.FallenPartsDestroyHeight`.</td>
|
||||
<td>Lower the destroy height or reduce the amount of parts that fall to the destroy height.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heartbeat/heartbeatInternal/workspaceOnHeartbeat/updateVisuallySleeping</td>
|
||||
<td>Second part of **notifyMovingAssemblies**.</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heartbeat/RunService.Heartbeat</td>
|
||||
<td>Runs functions connected to the `Class.RunService.Heartbeat` event to simulation and scripts contacts. Current description is generic enough to be not wrong.</td>
|
||||
<td>Reduce the amount or workload of functions connected to `Class.RunService.Heartbeat`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>worldStep/stepContacts</td>
|
||||
<td>Helps physics simulation step many contacts at once.</td>
|
||||
<td>Reduce the number of colliding objects.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SolveBatch</td>
|
||||
<td>Helps the physics simulation solve batches of objects' motion.</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## UI
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Description</th>
|
||||
<th>Performance advice</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Render/PreRender/UpdateInput</td>
|
||||
<td>Updates and fires all user input related events if the user has performed input since the last frame.</td>
|
||||
<td>Try and not do too much work directly as you get the input. Consider doing a minimal amount of processing for the input, and larger computations should be pushed off to another thread that happens later.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Render/PreRender/TweenService</td>
|
||||
<td>Updates objects being tweened using `Class.TweenService` and calls completion callbacks, such as those used provided to `Class.GuiObject:TweenSize()` or `Class.GuiObject:TweenPosition()`.</td>
|
||||
<td>Reduce the number of objects being tweened using `Class.TweenService` and ensure callbacks do as little work as possible.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heartbeat/TweenService</td>
|
||||
<td>In the server, `Class.TweenService` runs in `Class.RunService.Heartbeat` instead of `Class.RunService.PreRender`.</td>
|
||||
<td>Reduce the number of objects being tweened using `Class.TweenService` and ensure callbacks do as little work as possible.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Render/PreRender/UpdateUILayouts</td>
|
||||
<td>Updates position and size for UI elements on all enabled `Class.LayerCollector|LayerCollectors`.</td>
|
||||
<td>See **UpdateUILayouts** labels below.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Render/PreRender/UpdateUILayouts/Rebuild Z-order list</td>
|
||||
<td>Sorts the Z-order of UI elements (internal term not to be confused with `Class.GuiObject.ZIndex`) to prevent tearing of UI elements.</td>
|
||||
<td>Reduce the amount of UI elements with the same `Class.GuiObject.ZIndex`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Render/PreRender/UpdateUILayouts/Layout</td>
|
||||
<td>Updates position and size for UI elements in an individual `Class.LayerCollector`</td>
|
||||
<td>Reduce the amount of UI elements being resized or repositioned, such as those managed by `Class.UILayout` and those tweened with `Class.TweenService`, `Class.GuiObject:TweenSize()`, or `Class.GuiObject:TweenPosition()`. Consider using fixed sizes for `Class.BillboardGui|BillboardGuis`.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: Task scheduler
|
||||
description: Task Scheduler coordinates tasks done in each frame as the experience runs.
|
||||
---
|
||||
|
||||
The **task scheduler** coordinates tasks done each frame as the experience runs, even when it is paused. These tasks include detecting player input, animating characters, updating the physics simulation, and resuming scripts in a `Library.task.wait()` state.
|
||||
|
||||
While there may be multiple tasks running, the task scheduler can potentially be overloaded, especially in the following situations:
|
||||
|
||||
- Using a custom character rig or input scheme.
|
||||
- Animating parts yourself (instead of using an `Class.Animator`).
|
||||
- Depending heavily on precise physics.
|
||||
- Replicating objects regularly.
|
||||
|
||||
## RunService
|
||||
|
||||
The most direct way to add frame-by-frame tasks is through the following members of `Class.RunService`:
|
||||
|
||||
- `Class.RunService:BindToRenderStep()`
|
||||
- `Class.RunService.PreAnimation`
|
||||
- `Class.RunService.PreRender`
|
||||
- `Class.RunService.PreSimulation`
|
||||
- `Class.RunService.PostSimulation`
|
||||
- `Class.RunService.Heartbeat`
|
||||
|
||||
## Scheduler priority
|
||||
|
||||
The task scheduler categorizes and completes tasks in the following order. Some tasks may not perform work in a frame, while others may run multiple times.
|
||||
|
||||
<img src="../../assets/optimization/task-scheduler/scheduler-priority.png" />
|
||||
|
||||
## Best practices
|
||||
|
||||
To build performant experiences with efficiency in mind, note the following:
|
||||
|
||||
- **Don't connect/bind functions to the render step unless absolutely necessary.**
|
||||
Only tasks that must be done after input but before rendering should be done in such a way, like camera movement. For strict control over order, use `Class.RunService:BindToRenderStep()|BindToRenderStep()` instead of `Class.RunService.PreRender|PreRender`.
|
||||
|
||||
- **Manage physical states carefully.**
|
||||
`Class.RunService.PreSimulation|PreSimulation` happens **before** physics, while `Class.RunService.PostSimulation|PostSimulation` happens **after** physics. Therefore, gameplay logic that affects the physics state should be done in `Class.RunService.PreSimulation|PreSimulation`, such as setting the `Class.BasePart.Velocity|Velocity` of parts. In contrast, gameplay logic that relies on or reacts to the physics state should be handled in `Class.RunService.PostSimulation|PostSimulation`, such as reading the `Class.BasePart.Position|Position` of parts to detect when they enter defined zones.
|
||||
|
||||
- **Motor6D transform changes should be done on the PreSimulation event.**
|
||||
If you don't, `Class.Animator|Animators` will overwrite changes on the next frame. Even without an `Class.Animator`, `Class.RunService.PreSimulation|PreSimulation` is the last Luau event fired before `Class.Motor6D.Transform` is applied to part positions.
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: MicroProfiler walkthrough
|
||||
description: Explains how using the MicroProfiler can help you optimize portions of your experience.
|
||||
---
|
||||
|
||||
This walkthrough shows how to use the MicroProfiler to find a problematic aspect of an experience and identify the root cause. Download the experience, **Open from File** in Studio, and follow along.
|
||||
|
||||
<a href="../../assets/optimization/microprofiler/RaycastSpam.rbxl">
|
||||
<Button variant="contained">Download the Experience</Button>
|
||||
</a>
|
||||
|
||||
<br />
|
||||
|
||||
## Identify the issue
|
||||
|
||||
1. After you open the experience in Studio, start testing it with <kbd>F5</kbd> or the **Play** button.
|
||||
|
||||
1. The frame rate feels decent, but not as smooth as it should be for an experience of this size and scope. Press <kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>F5</kbd> (<kbd>⌘</kbd><kbd>Shift</kbd><kbd>F5</kbd>) to show the **Performance Summary** overlay.
|
||||
|
||||
<img alt="Performance summary showing 45 FPS." src="../../assets/optimization/microprofiler/micro-tut-framerate.png" width="500px" />
|
||||
|
||||
Note that the frame rate is below 60 frames per second (FPS).
|
||||
|
||||
1. Open the MicroProfiler by pressing <kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>F6</kbd> (<kbd>⌘</kbd><kbd>⌥</kbd><kbd>F6</kbd>).
|
||||
|
||||
<img alt="The MicroProfiler after opening it." src="../../assets/optimization/microprofiler/micro-tut-bar-graph.png" width="500px" />
|
||||
|
||||
Note that the frame times are **consistent**—the bars are roughly the same height—so whatever is causing the low frame rate is running every single frame as opposed to running occasionally and causing frame time spikes.
|
||||
|
||||
1. Pause the MicroProfiler by pressing <kbd>Ctrl</kbd><kbd>P</kbd> (<kbd>⌘</kbd><kbd>P</kbd>). Pausing with the keyboard shortcut opens [detailed mode](./modes.md#detailed-mode).
|
||||
|
||||
1. Click and drag to pan the graph. Note how one particular task in the worker threads seems to be using a lot of processing time compared to the main thread. Hover over `Class.RunService.Stepped` and note how long it's taking to render.
|
||||
|
||||
<img alt="Detailed mode with long labels for processes." src="../../assets/optimization/microprofiler/micro-tut-stepped.png" width="600px" />
|
||||
|
||||
1. Stacked bars in the timeline indicate a hierarchy of code, so keep hovering on lower and lower layers to try and identify the issue.
|
||||
|
||||
<img alt="On-hover details for various processes, with LocalScript highlighted." src="../../assets/optimization/microprofiler/micro-tut-localscript.png" width="600px" />
|
||||
|
||||
<img alt="On-hover details for various processes, with Raycast highlighted." src="../../assets/optimization/microprofiler/micro-tut-raycast-label.png" width="600px" />
|
||||
|
||||
Note the `LocalScript` label, which indicates the name of the script, and the `Raycast` label, which indicates that the problem might be related to raycasting.
|
||||
|
||||
## Create troubleshooting labels
|
||||
|
||||
Now that the MicroProfiler has provided a starting point, you can troubleshoot the problematic code.
|
||||
|
||||
1. Stop the play test and filter the Explorer window for `localscript` to find the file.
|
||||
|
||||
<img alt="A view of filtering in the Explorer window." src="../../assets/optimization/microprofiler/micro-tut-explorer-filter.png" width="400px" />
|
||||
|
||||
A search for `raycast` indicates that this portion of code is probably the culprit for the experience's poor performance:
|
||||
|
||||
```lua
|
||||
local RAYS_PER_SECOND = 1500
|
||||
|
||||
local function onStepped()
|
||||
|
||||
for _ = 1, RAYS_PER_SECOND do
|
||||
local startPosition = getRandomPosition()
|
||||
local endPosition = getRandomPosition()
|
||||
local direction = endPosition - startPosition
|
||||
|
||||
Workspace:Raycast(
|
||||
startPosition,
|
||||
endPosition
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
RunService.Stepped:Connect(onStepped)
|
||||
```
|
||||
|
||||
Specifically, you can see that the code is casting 1,500 rays per second in random directions.
|
||||
|
||||
1. To verify that this portion of code is causing the rendering delay, wrap the contents of the function with `Library.debug.profilebegin()` and `Library.debug.profileend()`:
|
||||
|
||||
```lua
|
||||
local function onStepped()
|
||||
|
||||
debug.profilebegin("Raycast Spam")
|
||||
|
||||
for _ = 1, RAYS_PER_SECOND do
|
||||
local startPosition = getRandomPosition()
|
||||
local endPosition = getRandomPosition()
|
||||
local direction = endPosition - startPosition
|
||||
|
||||
Workspace:Raycast(
|
||||
startPosition,
|
||||
endPosition
|
||||
)
|
||||
end
|
||||
|
||||
debug.profileend()
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
## Confirm and fix the issue
|
||||
|
||||
1. Start testing the experience, and open the MicroProfiler again.
|
||||
|
||||
1. Note how the MicroProfiler now shows the custom label, indicating that this function was indeed the root cause.
|
||||
|
||||
<img alt="MicroProfiler detailed view with 'Raycast Spam' visible." src="../../assets/optimization/microprofiler/micro-tut-spam.png" width="500px" />
|
||||
|
||||
1. Stop the play test.
|
||||
|
||||
1. Delete or comment out the `onStepped()` function and the `RunService.Stepped:Connect(onStepped)` connection.
|
||||
|
||||
1. Start testing the experience again, and check the **Performance Summary** again.
|
||||
|
||||
<img alt="Performance summary showing 60 FPS." src="../../assets/optimization/microprofiler/micro-tut-framerate2.png" width="500px" />
|
||||
|
||||
Note the huge improvement to frame rate and corresponding drop in frame times on the MicroProfiler graph.
|
||||
19
.cursor/rules/roblox/performance-optimization/monitor.md
Normal file
19
.cursor/rules/roblox/performance-optimization/monitor.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
title: Monitor performance
|
||||
description: Explains how to use the Performance Dashboard to monitor your experiences.
|
||||
---
|
||||
|
||||
The [Performance Dashboard](../production/analytics/performance.md) is the best way to monitor the performance of your published experiences. The two tabs include real-time performance information for both clients and servers, and you can use date ranges to track the impact of your changes over time.
|
||||
|
||||
For example, imagine you've just added a new place to your experience:
|
||||
|
||||
1. You publish the updated experience, and hours later, you notice a distinct uptick in client crash rate on the Performance Dashboard that seems to correlate with client memory usage.
|
||||
1. You test the new place in Studio and don't see any issues. Memory usage seems similar to other places in your experience.
|
||||
1. You note that average player session time has increased dramatically since your update.
|
||||
1. You test again, but this time, you spend more time in the experience and keep a closer eye on memory usage over time. Sure enough, usage is growing over time and could cause crashes on lower-end devices.
|
||||
1. You check the server tab and notice that server memory usage is growing over time, too. It's not a problem yet, but could become a problem soon.
|
||||
1. Under the **Memory** tab of the Developer Console, in the **PlaceScriptMemory** breakdown, you notice that the memory usage for one of your new scripts grows each time you interact with some key objects in the world. The script runs on both the client and the server.
|
||||
1. You check the script, which adds some user data to a large table, but never cleans the data up—it's growing infinitely.
|
||||
1. You fix the problem, publish the updated experience, and client crash rate returns to its previous value.
|
||||
|
||||
Comparing the Performance Dashboard to other dashboards can help you correlate performance improvements (or regressions) to business metrics for a more complete idea of how a performant experience impacts your bottom line. For more information, see [Analytics](../production/analytics/index.md).
|
||||
Loading…
Add table
Add a link
Reference in a new issue