Performance: Why you should care, when you should care

“Performance” means a lot. It can mean call time optimization, it can mean low memory usage. It could mean lowering bandwith usage, or maybe all of the above. Performance could be about something per-frame, or something that gets called once. It could be about reducing start-up times, or reducing load times. Or it could be all of those things!

I’m going to attempt to explain when performance matters and why it matters. There’s a lot of misinterpretations and people saying “don’t use xx because it is 100 microseconds slower”. Microseconds can matter when you’re dealing with things like frame times, but does it really matter when you’re running the code once per second?

Frame Time / Frame Budget

So first off: let’s define what fps is. A frame, or a “tick”, or how long it takes to run things like rendering and server logic. For example, it takes 16 milliseconds to render a single frame and display it on your screen. This means you have 60 frames per second- which is what Roblox runs at.

One important thing to note is that 16 milliseconds is a lot of time. 1 millisecond is 1000 microseconds- a difference of 80 microseconds is pointless. It’s a micro-optimization. One common example of needless performance worries is with object-oriented programming- the difference between regular function calling, and method calling, is negligible (within ~80 microseconds), and most of the time you won’t be calling something per frame with object-oriented anyways. However something that saves ~800 microseconds is worth it- that’s almost 1/16th your frame budget.

image
(Benchmarking method vs. passing self)

One important thing to note is almost no optimization that sacrifices readability for speed is not worth it 90% of the time.
Making your code unreadable for a ~150 microsecond gain is not worth it.

Ping is directly impacted by frame time. Due to the way Roblox networking works (packets are sent at the end of the frame, packets are received at the start of the frame), your frame time directly impacts ping. Going under 60fps will increase your user’s ping.

It’s important to note that the more remotes you call, the longer it’ll take to send those out. This literally increases frame time- which increases ping. Connections are also atrociously inefficient.

The Microprofiler

Since Roblox’s fps-cap is at 60 (for both the client and server), that means each frame should take under 16 milliseconds- since 1/60 is 0.016, which is 16 milliseconds. So this is the maximum amount of time it takes before user experience starts dropping. The microprofiler lets us see what exactly is eating up the frame budget, and it’s a very useful tool for debugging performance issues.


(A screenshot of the client-sided microprofiler)

Networking

Networking is arguably the most important area to optimize. Optimizing your networking can significantly improve user experience by the frame time on the server (which subsequently reduces ping), ping spikes, and in some extreme cases prevent disconnecting from the server. In other extreme cases, not optimizing bandwith can result in long freezes on the server. However I’ve already talked about this, and so have other developers- I’ll just link the posts below.

Client FPS

This is arguably one of the most important factors of optimization. Your client’s FPS should be top priority, as a sub-60 FPS experience may significantly diminish the quality of time spent on your game. There’s a lot of great ways to optimize the client though:

  • Implement a chunkloading system
  • Turn off Shadows and CastShadow on things that don’t need them
  • Delete textures the client will never see
  • Don’t run heavy code on the client- optimize all your code on the client. It directly impacts FPS.
  • Don’t overlap textures- this significantly increases render time.
  • Transparent blocks / textures are substantially worse for performance.

Conclusion

You should focus on optimizing client fps, server fps and bandwith- you shouldn’t be focused on things like reducing call time for functions that get called once per second. Your frame time is what matters.

I don’t recommend using attributes, valueobjects and instances for things like state. This isn’t good, but I have an entire post about that.

39 Likes

One other important thing here is avoiding frame spikes. This causes occasional frames to take significantly longer than average, which is often noticeable to players. Frame spikes often happen when client code runs intensive code all at once. Programmers can avoid these by breaking up large pieces of code using something that yields (wait(), task.wait(), etc).

An example of a situation where this might happen is checking all the players in the server and doing some mildly intensive code for each one ever second. The code for each player should ideally be broken up so it’s execution can be spread out and not hold up the frame. Here is an example:

while task.wait(2) do
    for _, player in ipairs(Players:GetChildren()) do
        -- Intensive code
    end
    -- Only adds a little bit of time but makes it so all the code
    -- isn't run in a single frame (which can prolong it)
    task.wait()
end

Also an important note is that optimizing a game for computers+consoles is different than for mobile. For mobile the biggest limiting factor is usually memory, while for computers and consoles it’s often CPU (can be a bit of both though in more extreme cases).

Overall, great tutorial! Thanks for making this :smiley: It’s really helpful to have sections for pretty much all areas of performance improvement in a single place.


Some related resources:

Good general tips.

More details about using the micro profiler.

Great video from RDC. Some of it is a little outdated, so take it with a grain of salt. Goes into a lot of detail about game preformance.

7 Likes

I’m curious if you have any tips for impementing a chunkloading system with non-static instances. Currently I’m making a game with different chunks and my plan is to send an initial state for the chunk then send changes after that. I’m worried though that problems in my code might result the client becoming out of sync. Do you have any tips or suggestions?

I love topics like this! However I don’t think this is correct.

I think you mean translucent parts. I’m 99% sure that fully transparent or fully opaque parts are more performant than parts that have a transparency value that isn’t 1 or 0.

I think this was confirmed in a Roblox performance video by zeuxcg. The video was already posted by WarpedWormHole above.

Couldn’t StreamingEnabled work instead of your own system? I’m sure a built in system is far faster and more efficient than anything a developer could make on the clientside.

2 Likes

Yep! Meant translucent not transparent.

StreamingEnabled is not good. Firstly: you don’t need to cull everything. Culling everything means you get visual cutting and sometimes things like walls visually appearing. A custom culling system allows you to prevent that.
Secondly: StreamingEnabled eats performance on the server. I’ve seen profiles of StreamingEnabled taking over 5 or 6 milliseconds- and it scales with your player cap.
Third: StreamingEnabled is buggy. You don’t have control - meaning you need to build your game around StreamingEnabled. But there’s also a lot of bugs with it too relating to physics objects and humanoids.

A custom chunkloading system is way more efficient than StreamingEnabled.

2 Likes

Simple: Don’t unrender/render dynamic instances.

StreamingEnabled doesn’t do this well either, and Roblox doesn’t have remote reliability types - meaning whatever you do, this isn’t going to be pretty and it will have bugs.

1 Like