It’s a directive to the engine to compile the script into native machine code instead of the byte code for interpretation. Machine codes runs directly on the CPU at CPU speed, which significantly increases the performance of the code over interpretation.
--!native
is a flag that compiles Luau bytecode to direct machine assembly through a process called JIT compilation. The issue is Apple.
Apple. Apple and its hate for JIT compilation
PHP and JavaScript do this as well. I think other interpreted languages like Python and Ruby also do this. I haven’t heard anything about Apple hating JITC. Would you please elaborate? Have they stated any reasons?
apple only allows jit on web browser apps. they are pretty strict with this stuff as it could lead to security vulnerabilities, being able to generate executable code on the fly
Lua doesn’t use a stack because it’s register-based (it uses a return stack for function calls, but that’s unrelated). I have no idea what bug you are talking about because shifting registers should be impossible since they’re unsequenced.
Luau native codegen isn’t JIT-compiled, it’s AOT-compiled.
Still, Apple does not permit running dynamic assembly on its chips, to the hardware level. This is the same reason Minecraft had to be rewritten in C++, because the JVM wouldn’t work on iOS.
The bug I was trying to explain was on the native codegen announcement
It was also a weird bug, never got to reproduce it with simpler steps as whenever I changed even a loop it stopped functioning that way.
WheretIB had also verified this was a bug (yippe)
Yes, I meant the registers sorry for the misinformation. Will fix that now.
Here’s the issue that I have. Since the code is precompiled before the client downloads it, I wouldn’t call that dynamic assembly. The code is compiled on publish and then sent to the device when the device logs into the server. I don’t see the problem here.
I recall something about Roblox looking into client-sided native code.
I will bump this topic because it’s something I absolutely need.
I barely benefit from server-sided native code at all because the heaviest and most expensive logic in on the client.
While they’re at it, it would also be nice if they removed (or increased) the memory and instruction limit.
I want to choose when and what code becomes native.
I don’t use native codegen for everything, but Roblox still decides how many functions and whatnot you can use it on.
If you exceed the limit you have to start un-nativizing functions which is annoying.
What if 50% of my project can benefit from the native codegen? Just let me use it on half the modules then.
as? the native attribute is not for the client? I thought so, because I put it in my scripts and in the scriptProfiler section I get which indicates that it is native. I have lived a lie hahaha
As far as I know, native code currently only works on the client in studio.
In live games everything on client is still plain Lua(u).
There’s also an rather annoying limit for how much code you can make native.
Once you exceed a certain memory or instruction threshold it gives a warning or error and some functions don’t become native.
I wish Roblox had a “I know what I’m doing, remove my limits please!” option.
You can use @native
to tag functions to use native code gen. Client code gen is not easy since luau doesn’t use llvm
I’ve already been using @native
, but sometimes it turns out I might have more math functions than I anticipated.
I manually tag all of my functions and modules, function by function.
Tedious, but it gives the most control.
This is a bit off-topic, but until we await the native code generation on the client (if it ever will), I think we mind looking into something else called strict type checking. With proper types, we can remove some overhead for the interpreter and improve performance a bit. This works especially great for primitive data types
Oh, damn. Finally! I hope it stays like this and they don’t just randomly remove it.
This is the first time a feature request has been implemented in ages.
As far as I know what they meant in the announcement was that native codegen gets better speeds via types. Types don’t increase speeds by themselves, lua is a dynamically typed language while luau provides a typechecker ontop of it to ease development.
From my tests, it actually does affect the bytecode to some extent already. At least in --!strict mode
Huh? What do you mean change the bytecode?
I thought types were discarded during runtime, could I see your benchmark code?
Based on my own, (doing vector3 & scalar math without ncg) they seem 1:1.
Maybe I’m doing something wrong
Code
--!strict
-- strict typechecking speed benchmark, compared to non-typed.
local ITERATION_COUNT = 4096
local typedVectorMath: {number} = {}
local typedScalarMath: {number} = {}
local nonTypedVectorMath: {number} = {}
local nonTypedScalarMath: {number} = {}
--// A function to get the median percentile of the number array
local function calculateMedian(arr: {number}): number
table.sort(arr)
local n = #arr
return n % 2 == 0 and (arr[n//2] + arr[n//2 + 1])/2 or arr[math.ceil(n/2)]
end
local function formatTime(seconds)
if seconds >= 1e-3 then -- Millisecond range (>= 0.001 seconds)
return string.format("%.3f ms", seconds * 1000)
elseif seconds >= 1e-6 then -- Microsecond range (>= 0.000001 seconds)
return string.format("%.3f μs", seconds * 1e6)
else -- Default to nanoseconds
return string.format("%.3f ns", seconds * 1e9)
end
end
-- ===== BENCHMARKS ===== --
-- Typed vector math
for _ = 1, ITERATION_COUNT do
local begin = os.clock()
local vec: Vector3 = Vector3.new(1,1,1)
local vec2: Vector3 = Vector3.new(2,2,2)
local temp: Vector3 = vec * vec2
table.insert(typedVectorMath, os.clock() - begin)
end
-- Typed scalar math
for _ = 1, ITERATION_COUNT do
local begin = os.clock()
local a: number = 1.5
local b: number = -4.2
local c: number = 2.8
local d: number = 3.14
-- ax³ + bx²y + cxy² + dy³
local x: number = 2.5
local y: number = 1.8
local term1: number = a * (x^3)
local term2: number = b * (x^2) * y
local term3: number = c * x * (y^2)
local term4: number = d * (y^3)
local result: number = ((term1 + term2) * (term3 - term4)) / (x^2 + y^2)
table.insert(typedScalarMath, os.clock() - begin)
end
task.wait()
-- Non-typed vector math
for _ = 1, ITERATION_COUNT do
local begin = os.clock()
local vec = Vector3.new(1,1,1)
local vec2 = Vector3.new(2,2,2)
local temp = vec * vec2
table.insert(nonTypedVectorMath, os.clock() - begin)
end
-- Non-typed scalar math
for _ = 1, ITERATION_COUNT do
local begin = os.clock()
local a = 1.5
local b = -4.2
local c = 2.8
local d = 3.14
local x = 2.5
local y = 1.8
local term1 = a * (x^3)
local term2 = b * (x^2) * y
local term3 = c * x * (y^2)
local term4 = d * (y^3)
local result = ((term1 + term2) * (term3 - term4)) / (x^2 + y^2)
table.insert(nonTypedScalarMath, os.clock() - begin)
end
-- ===== RESULTS ===== --
print("Typed Vector Math (Median %ile):", formatTime(calculateMedian(typedVectorMath)))
warn("Non-typed Vector Math (Median %ile):", formatTime(calculateMedian(nonTypedVectorMath)))
print("Typed Scalar Math (Median %ile):", formatTime(calculateMedian(typedScalarMath)))
warn("Non-typed Scalar Math (Median %ile):", formatTime(calculateMedian(nonTypedScalarMath)))
This initially got my hopes up… until I realized you tried this in Studio.
Just to clarify, NCG has always been enabled by default in Studio, working with both LocalScripts and ServerScripts.
However, after testing this on live servers, I can confirm it’s still not yet enabled there.