We’re excited to share an early preview of our “Faster Lua” project with you today!
What is it?
As the platform grows, we see more and more use of Lua and as a result, it’s more and more important to make sure Lua scripts run efficiently. We’ve spent some time over the last few years optimizing our reflection layer - the system that allows Lua scripts to communicate to the engine - but we’ve never done anything about Lua itself.
With the goal of having Lua run faster on all our platforms, we’ve evaluated a set of approaches and decided to build a new Lua implementation because we’re crazy. The goal is complete feature parity - scripts that run in the old VM should run in the new VM - and better performance.
We have written a brand new compiler and a brand new interpreter, and we’ve made a few changes to the reflection layer and garbage collector, but we haven’t changed the standard library, so depending on the performance characteristics of your code you will see varied gains. Reflection access got a bit faster in both old VM and new VM as a result of this work as well.
In usual scripts we see a mix of reflection access and other work - depending on what the scripts do you can get all the way between “it’s not faster at all” and “it’s several times faster” as a result. Our builtin terrain generator is about 2x faster with new VM, and some scripts we’ve looked at are up to 3x faster but it’s more common to see more moderate gains.
New VM also produces bytecode that’s a bit smaller than old VM does; in particular we no longer send local variable names to the client, which will make decompilers slightly less useful.
How do I get it?
Assuming you are enrolled in our beta program (if you aren’t, you’ll want to read this), you can access the list of beta features in the File menu:
And then enable this beta feature (don’t forget to restart Studio!):
After this, new VM will be active in Studio for all script execution, including game scripts, core scripts, plugins and command bar.
Debugger doesn’t work!
We currently only support a limited amount of interaction with the debugger - break on error and inspection of the variables on the call stack should work, but breakpoints and stepping doesn’t work yet.
We’re actively working on implementing debugger support in the new VM; we decided to share this early preview even though it’s not ready.
Note: in old VM, enabling debugger would adversely affect performance of game scripts in Studio; new VM will have a zero-overhead debugger (no overhead when debugger is enabled but you aren’t actively interacting with it), which will mean that performance in Studio test modes accurately reflects desktop performance.
My plugin / game breaks when I enable this!
Our intention is to support all existing Lua code that runs on Roblox platform without changes. We don’t know if it’s going to be possible but that’s the hope. If you have any scripts that misbehave in the new VM, please report this on this thread so that we can investigate this and, hopefully, fix this.
My game works! How do I get this performance benefit on actual client/server?
We plan to enable the new VM fully for all existing games on client/server eventually, but we want to be careful about doing this.
If your game is popular, you have tested your game in Studio and you would like us to enable the new VM for your game, please let us know (note that we won’t be able to do this immediately but we’ll try to do this in the following weeks, and we’ll personally notify devs who requested this once it’s live).
Once enough popular games have the new VM running well on production and we’re pretty sure we don’t have any outstanding bugs, we’ll try to enable the new VM for all existing games; once we confirm that there are no issues with the new VM anywhere, we will deprecate and remove the old VM.
How do I know what Lua constructs are fast in the new VM and what are slow?
We have a host of different optimizations in the new VM, and some of them render some performance advice, such as the necessity to cache certain table lookups like
It’s also worth noting that in the new VM, you pay a more substantial penalty for use of
setfenvfunctions. We strongly recommend not using either of these in any scripts if at all possible, and to stop using module systems that inject globals using
setfenvin favor of more traditional use of named require imports e.g.
local module = require(Path.To.Module).
We are going to publish a script performance guide in the coming months that describes the performance characteristics of common Lua constructs in detail; also I will give a talk about this on RDC in August, which you’re welcome to attend or tune in to if we’re streaming it.
I want my scripts to run even faster!
We have more performance tuning we’d like to do on the new VM this year, so if you have code that’s particularly performance heavy feel free to share it with us and we’ll see what we can do! We are also starting to look into what it would take to enable multi-threading support for Lua but this will not happen this year.
Behavior / compatibility changes in new VM
game("GetService", "RunService")is no longer a valid way to invoke the GetService method. This way of calling methods was an accidental side effect of how we implemented
namecall; for new VM we have revised the namecall implementation to be more efficient which means that this no longer works. Note that this was never a supported extension to Roblox Lua and we have warned about this breaking in the future without notice.
There’s now a limit on the nesting depth of complex expressions; previously
0+0+...+0could compile for an arbitrary amount of intermediate additions, we now limit the depth of expression trees to a reasonable value (the current limit is 1000).
Local variable names and upvalue names are no longer mentioned in the error messages because VM no longer tracks them (except in the debugger)
0/0 and some other constant expressions of this kind produce 0 instead of NaN on 64-bit Windows Studio.This has been fixed in Studio 392.
Scripts with a lot of different integer numbers take a lot of time to compile in 64-bit Studio.This has been fixed in Studio 392.
Indexing unassigned globals (This has been fixed in Studio 394.
print(a.a)) results in
When compiling very long chains of method call expressions, such asThis has been fixed in Studio 396.
obj:Method1(1,2):Method2(1,2):....., compiler may run out of registers.
When compiling multi-line string literals with embedded Windows-style line endings, the resulting literal contains \r characters.This has been fixed in Studio 396.