[Improved] How to Actually Improve Performance in Your Games

In my last post, I went over how to “improve performance and set performance goals” — that post is incredibly outdated and full of terrible advice. :man_facepalming: Do not follow it

It was very vague and my knowledge as a scripter & programmer has improved a lot
This topic will list a bunch of useful things that have helped many projects I’ve worked on and I’ll split it into 2 parts: For Scripters & For Anyone!

Note: The information provided here is updated in response to feedback and suggestions! I have been getting busier so some information may become outdated!

For Anyone:

  • CanTouch, CanCollide & CanQuery
    BaseParts (Parts, MeshParts, Unions, etc) have the .CanTouch property which when set to false along with .CanCollide will have a small performance improvement and can still be hit by Raycasts and Region3 queries (so it’s best to set both of those properties to false on objects that don’t need to calculate physics and won’t be interacted with).
    CanQuery is another property that can be disabled for a small performance gain. To my knowledge, it’s pretty new.

  • Lighting
    If your game does not need the ShadowMap or Future technologies, by all means, don’t use them. Something as simple as using the Voxel lighting can make your game’s performance do a 180 in terms of lag! Rendering fewer shadows may also give your game small performance boosts (this may not produce noticeable gains and is not intended to be a performance-enhancing feature by Roblox); you can disable the .CastShadow property on BaseParts that you don’t want to cast shadows for that (potentially) small boost. [Lighting]

  • Collision & Render Fidelity
    Unions and MeshParts have this wonderful .CollisionFidelity property which when set to box will change the hitbox of the Union or MeshPart to a box! This means less work for the server or client! BaseParts and Meshparts have the .RenderFidelity property, which when set to performance will change how the Instance looks when at a longer distance from the player.

  • Teleporting
    With teleportation you can teleport your players between places and massively increase the size of your game by splitting the game up! If there’s less to load, there’s less to load!

  • Useless Replication
    If something shouldn’t be replicated (shown) to the client then don’t put it in places like ReplicatedStorage, put it in a place like ServerStorage. Anything the client can see must first be replicated to them, which takes valuable resources. (Edit: You can check out more information on that here.)

  • ANCHOR
    Make sure you anchor things that shouldn’t move! I’ve worked with developers who have front-page games who just “forget” to anchor parts (you know who you are)! This makes Roblox calculate more physics, thus, causing beautiful lag.

  • Transparency
    While small, it is worth noting that the .Transparency property exists! In Roblox’s own words (here) " Use partial object Transparency sparingly, as semi-transparent objects inhibit a lot of internal rendering optimizations. If you require transparency on several BaseParts , attempt to merge them using solid modeling (a transparent union is more optimal than a group of 10 smaller transparent parts)."

  • Textures
    Textures are amazing if you want more than Roblox’s materials but they come at a cost. You should use the built-in materials (and now the MaterialService) in favor of textures because textures occupy more memory than materials (GraphicsTexture in the Developer Console under Memory). If you want to use textures I don’t advise against it, especially since you can really solidify a game’s feeling with textures!

  • Player Count
    Stress test your game! See how well it performs with a bunch of players and reduce the MaxPlayers you can have in one server!

  • Humanoids
    As a lot of developers know: humanoids can be very laggy when there are a lot of them together. You can disable HumanoidStateTypes you won’t use but in my experience, it appears to make no difference. You can also just stop them from moving and calculating physics by anchoring and changing properties. I recommend just not having dozens of humanoids near each other! If you were willing you could even make enemies or NPCs that don’t rely on the humanoid (that’s my personal favorite).

  • Content Streaming
    Please read the official documentation here. They explain it way better than I could.

In-experience content streaming allows the Roblox engine to dynamically load and unload 3D content and related instances in regions of the world.

For Scripters:

  • Infinite Loops
    Good ol’ while true do … I’m sure every scripter knows not to have tons of these. If you do, try using .Changed, :GetPropertyChangedSignal or parts of the RunService! Try not to use infinite loops where they aren’t needed!

  • FindFirstChild
    Roblox says it best:


    I’m not discouraging the use of FindFirstChild, I’m discouraging the use of FindFirstChild where it isn’t needed

  • Wait()
    Use task.wait() instead of wait(). This is explained later in the post.

  • Instance.new()
    Using Instance.new("Instance", setParent) and setting the parent with the second parameter is actually slower than instancing then setting the parent on another line, like local instance = Instance.new("Whatever") and then instance.Parent = whatever
    Source

  • Connections
    (See Roblox’s Handling Events)
    This one might be rushed until I can refine it but here’s the basics: You can use the Connect() function to “connect” events to a function, you probably do this a lot already but might never use Disconnect(). The link listed above will go in more depth but do note that using Destroy() on a script or an ancestor of a script will disconnect all connections and you may not need to disconnect functions if a script will soon be removed this way. You can also use Once() instead of Connect(), this disconnects after the event is fired!

  • _G & Global Variables
    This post explains _G the best and reinforces the idea that _G is not slower than module scripts. A lot of scripters call _G bad practice when it’s just how you use it. If you’re storing everything in _G then yes, it’s bad practice. Using it in general is not bad and it’s down to preference and organization.

  • Repeating Expensive Calculations
    Don’t repeat expensive calculations (if you don’t need to)! Store the results of something like :GetChildren() in a variable when possible.

An example is in one of my games. I wanted to give every player a point if they’ve completed a task during a specific trigger or remove points if they failed, and slowly increase points lost the further in the game they were.

-- Bad Example
for index, player: Player in ipairs(Players:GetPlayers()) do
	local losePoints = 5 -- Stores the points they should lose in the loop (BAD)
	
	if round >= 50 -- If these were a lot of expensive calculations it would be laggy
		losePoints = 25 -- Because it runs once for each player in the game
	elseif round >= 20 then
		losePoints = 20
	elseif round >= 15 then
		losePoints = 10
	end
	
	if isTask1Complete or isTask2Complete then
		points.Value += 5
	else
		points.Value -= losePoints
	end
end

-- Good Example
local losePoints = 5 -- Stores the points they should lose OUTSIDE the loop instead!

if round >= 50 then -- If these were a lot of expensive calculations it would perform better!
	losePoints = 25
elseif round >= 20 then -- (Yes I know I shouldn't compare each round in a series of elseif statements if I'm worried about scalability, I quickly wrote this up as an example)
	losePoints = 20
elseif round >= 15 then
	losePoints = 10
end

for index, player: Player in ipairs(Players:GetPlayers()) do -- Tidy loop
	if isTask1Complete or isTask2Complete then
		points.Value += 5
	else
		points.Value -= losePoints
	end
end

Hey! The task library has great alternatives to wait() and spawn()!
task.wait() is the same as RunService.Heartbeat:wait() but takes less time to write and with just task.wait() is safe!

FUN FACT

Scripters! You can use operators like += and -= instead of x = x + whatever
When I learned this it saved me so much time!

You can also use FindFirstChild(“Name”, true) to loop through descendants (looping through a bunch of objects will have a delay)

I whipped this topic up in just a few hours of research and will likely be adding more to it in the future. If you have any suggestions please suggest them!

372 Likes

This really helpful to optimize lag in games and I’ve learned a lot. I suggest including a part it tells about some deprecated features in scripts to avoid and some alternatives.

  • Global functions & variables
  • :connect
  • Instance parent parameter
  • etc.
10 Likes

This is not entirely true. While rendering terrain is indeed costly, it only affects the visible surface. Terrain is stored in block shaped volumes based on common material and occupancy values. This means that an enormous cube shaped block of terrain will have a much less important memory footprint than small, but complex terrain.

Adding onto what I said, I’ve seen a lot of people removing terrain under the surface in an effort to improve the performance, but with the opposite result. When working on terrain, leave the underground portions filled out. Not only will it look better due to how occupancy works, but the performance will also be superior.

while loops cannot be used for checking property changes unless there’s something yielding the thread like a wait. If you don’t yield the code on each cycle, Roblox won’t be able to change the wanted property, essentially causing the old “why do while loops crash my game” issue. while true do loops are treated as infinite loops by Lua, meaning there isn’t much of a negative impact as long as the code inside them is properly handled.

You should have explained why. wait() is a relic code from Roblox’s past before we had the ability to bind to per-frame events such as Heartbeat and RenderStepped and the frame rate was capped at 30.

wait() uses a polling system that queues tasks until they are ready to be resumed. This means that one task will be resumed (if there is one to resume) every 2nd frame (remember the 30FPS part), meaning some tasks might never even resume if there are a lot of tasks in front of them. In other words, your 1 second wait might turn into a 10 hour wait if a lot of threads are yielded.

The same issue is observable in spawn() as it calls wait() before calling the parsed function.

19 Likes
  • Teleporting
    Teleporting (to me)

The problem with “(to me)”, is that guides like this which should only diverge objective information, and don’t really have room for a personal opinion. So, in this case, if something is better, it is better.

On its own , StreamingEnabled doesn’t unload anything, you’d have to do that.

Completely untrue, why do you think the server is so involved when it comes to streaming enabled? Why do you think StreamingEnabled is primarily offered as a solution to assisting performance on low memory devices? It’s because things that are streamed out, literally do not exist on the client. This is precisely why you’re supposed to change your game design in how you retrieve things on the client side of your game, because they no longer exist. When the player is in radius, the server sends those missing instances over then net.

Teleporting has it’s own problems, not only is (in it’s current state) game teleportation very flawed and buggy, but you kill consistency (for example, leaving behind that player they liked whom they met 5 minutes ago but don’t quite want to friend) as well as things that only persist per server. Experiences where you don’t seperate the game are actually more immersive anyway due to having a single loading screen, moreover it’s actually easier to develop on a single place. No need for working with packages, pushing code over to another game, etc

  • Useless Replication
    If something shouldn’t be replicated (shown) to the client then don’t put it in places like ReplicatedStorage, put it in a place like ServerStorage. Anything the client can see must first be replicated to them, which takes valuable resources. (More on that here)

While this is decent advice on it’s own, the average instance is only like a kilobyte or two. Don’t know if i would call it valuable

  • Terrain
    Roblox loves to push terrain. While it is optimized. You should remove terrain that the players won’t interact with or see as it’s still there! Roblox’s terrain and especially water aren’t going to give you amazing results when there’s a bunch of it. Sometimes you might want to use parts instead but if ration out how much terrain you use in association with all of the other tips on this list you should do great!

Don’t know about that one chief, roblox already has it’s own culling solutions for terrain, it’s why you can see the amount of tri’s rendered being sharply changed when you move your camera in a terrain game through roblox’s debug menus. And recommending people to make their own terrain deleter is not a good idea lol, it’s a CPU bound operation that runs almost constantly, which is going to result in a laggier experience for low end devices with worse CPUS.

  • Transparency
    While small, it is worth noting the .Transparency property exists! In Roblox’s own words (here) “a transparent union is more optimal than a group of 10 smaller transparent parts”

Speaking of rendering, culling, etc. You should also mention here that semi-transparent parts are more expensive than non-transparent parts instead of recommending to use a union. (worth noting unions aren’t very lag friendly themselves lol)

FindFirstChild
Roblox says it best:

I know this came from roblox, but atleast from that snippet, that’s not very sound advice. Discouraging a safer method just because it saves a fraction of a milisecond is not a good thing to do. In the case of instance indexing (where you’d even mention :FindFirstChild() in the first place) Indexing an instance with something it doesn’t have will result in an error, therefore FindFirstChild is a much safer thing to use.

25 Likes

Thank you! This helped a lot! Very useful, bookmarked this tab!

3 Likes

Can confirm storing items in replicated storage when not in use is a bad idea! Never knew this was a thing, wiped out 200 mb of memory! This probably scales with how many items is stored in replicated storage in which I had a lot.
Before
image
After
image

21 Likes

Hopefully this helps with the lag in sl! Congrats:)

4 Likes

I’m glad spreading this information is helping people!

6 Likes

What should I be storing in ReplicatedStorage?

Currently I have clientside only modules, sounds and remote events/functions. Is this fine?

8 Likes

yeah that’s fine, mostly just models and things you don’t regularly show to the client I moved to ServerStorage

8 Likes

I’ve heard this a couple of times and the reasons behind it, but I never got an answer of what I should use instead. I don’t know if you could explain that, since I normally use wait(<0.05) to create intense flickering lights or other stuff which I don’t really remember.

3 Likes

RunService.Heartbeat is a good replacement. There’s also a few “Wait v2” modules on the devforums in this category that you can try out!

7 Likes

This is pretty useful for beginner scripters :+1:. And making code faster in general.

5 Likes

Not that this may be a good method, but I do find significant performance boosts by selecting what should be inside workspace.

If a player is far away from a certain room or area, and you believe the player has no intent to even be able to see it, shove it into a table, and parent it to nil. Once a player gets back nearby that the player should see the parts, parent it back to the workspace.

This removes the entire rendering, collisions, lighting, transparency, textures problem of the selected part if it’s still in the Roblox render distance.

However, at a cost to still keep the part in memory unless you intend to use a replication service to handle loading in parts/models in a chunk fashion to let clients :Destroy() items instead of holding them in nil.

4 Likes

The code you’re running in a short amount of time is where most of the problem forms!

3 Likes

Sorry for the late response, but about StreamingEnabled, I meant that it caches the already loaded parts of the game. If you go into a game with StreamingEnabled, you likely won’t see that far, once you load it in, it will stay loaded until you cannot render it, which then I assume it’s removed from the client until it’s back in their view

2 Likes

Also! Added some clarification for the terrain part! image

4 Likes

I have 62k parts in ReplicatedStorage (for testing) and using 268 mb of memory (default for creating a game), so I’m not sure if this is a myth or a fact.

2 Likes

Definitely fact! It’s listed on the reference API, anything in ReplicatedStorage is replicated to the client! Memory isn’t always directly correlated to performance!

2 Likes

This is entirely incorrect and a misinterpretation/contradiction of the actual article you quoted. The same article you quoted for the transparency section itself says:

Use partial object Transparency sparingly, as semi-transparent objects inhibit a lot of internal rendering optimizations.

Please fix this asap. I understand it’s probably a typo, but actually advising the complete opposite of what to do in a guide is unacceptable.

5 Likes