Why does removing whitespace speed up your code?

For example if you run this code here, the second, condensed, version is about twice as fast as the first version

local tick = tick
local count = 1000000

do
	local start = tick()
	for i = 1,count do
		local a = i
	end
	print(tick() - start)
end

do
	local start=tick() for i=1,count do local a=i end print(tick()-start)
end
1 Like

I always feel the need to compress some functions, if this is true, I’m glad I have this habit. Hahaha

1 Like

You should use @stravant’s plugin that automatically does this ; P

4 Likes

The difference is so insignificant that the tick() function wouldn’t be accurate enough to log the difference. You are losing readability for essentially .000000000000001% performance increase.

Not a good trade at all

3 Likes

I don’t believe it would speed your code up, but may speed up the initial compiling of the code. Even so, this is micro-optimization and will have no noticeable effect.

1 Like

@General_Scripter
guys the script is literally showing its twice as fast - and I’ve noticed sometimes it gets even faster (for entire modules not just for this individual operation)

What I do is have a script that compresses everything before I publish but I keep an un-condensed version to work off

Thats what I would think too but then why does tick show this!!

1 Like

Shouldn’t affect speed at all, the bytecode is the same

7 Likes

Does lua compile as the program runs or something?
Because it seems like if I add a delay before the rest of the code runs then it becomes equivalent

wait(3)
local tick = tick
local count = 1000000

do
	local start = tick()
	for i = 1,count do
		local a = i
	end
	print(tick() - start)
end

do
	local start=tick() for i=1,count do local a=i end print(tick()-start)
end
1 Like

Oh that’s weird, it does seem to get compiled differently based on whitespace, even though it shouldn’t. Lemme actually check the bytecode for a second

4 Likes

Maybe try switching them around so the faster version goes first

It doesn’t exactly follow logic but maybe the first one runs slower than the second for some reason?

It still happens with the condensed being faster unless there’s a delay

1 Like

Lua is an interpreted language, so the differences are literally the amount of time it takes to ignore stuff as it interprets on the fly. It’s so minuscule of a difference that it would be foolish to minify your code for performance reasons. Interesting observation though.

I’m not an expert on the Lua interpreter, so anyone feel free to correct me on that.

Code on the internet is minified for the sake of download size, not performance: Smaller files mean that clients will load scripts faster.

5 Likes

;o According to https://en.wikipedia.org/wiki/Lua_(programming_language)#Implementation

1 Like

In regular Lua it’s exactly the same bytecode, as it’s supposed to be.

@Crazyman32 since Lua gets parsed before being executed, it shouldn’t matter for that loop.
If you’re measuring time from inside the code, the code is already executing, so you can’t measure parsing difference.

Dumping the bytecode in a second

1 Like

Oh, maybe I’m getting confused with the LuaJIT compiler. Or I’m just wrong both ways. Anyway, that makes this much more interesting. I would be interested to see the dumped bytecode.

I think that while in studio, code is compiled into bytecode as it runs which is why the condensed version is faster, but I just tested in a live game on both server and client, and both versions ran at about the same speed on both client and server, so maybe Roblox precompiles like the other option Wikipedia says (same source) (which would also be more secure so exploiters can’t steal the lua text code directly):

Or that might be completely wrong because the running might be slightly delayed by other scripts while in the mean time the code is still compiled in parallel?

	local tick = tick
	local count = 1000000

	do
		local start=tick()
		for i=1,count do local a=i end
		print(tick()-start)
	end
	
	do
		local start = tick()
		for i = 1,count do
			local a = i
		end
		print(tick() - start)
	end
-- globals
0032  05000000           [01] getglobal  0   0        ; tick	
0036  41400000           [02] loadk      1   1        ; 1000000	
-- first block
003A  80000000           [03] move       2   0      	
003E  9C808000           [04] call       2   1   2  	
0042  C1800000           [05] loadk      3   2        ; 1	
0046  00018000           [06] move       4   1      	
004A  41810000           [07] loadk      5   2        ; 1	
004E  E0000080           [08] forprep    3   1        ; to [10]	
0052  C0010003           [09] move       7   6      	
0056  DF40FF7F           [10] forloop    3   -2       ; to [9] if loop	
005A  C5C00000           [11] getglobal  3   3        ; print	
005E  00010000           [12] move       4   0      	
0062  1C818000           [13] call       4   1   2  	
0066  0D810002           [14] sub        4   4   2  	
006A  DC400001           [15] call       3   2   1  	
-- second block
006E  80000000           [16] move       2   0      	
0072  9C808000           [17] call       2   1   2  	
0076  C1800000           [18] loadk      3   2        ; 1	
007A  00018000           [19] move       4   1      	
007E  41810000           [20] loadk      5   2        ; 1	
0082  E0000080           [21] forprep    3   1        ; to [23]	
0086  C0010003           [22] move       7   6      	
008A  DF40FF7F           [23] forloop    3   -2       ; to [22] if loop	
008E  C5C00000           [24] getglobal  3   3        ; print	
0092  00010000           [25] move       4   0      	
0096  1C818000           [26] call       4   1   2  	
009A  0D810002           [27] sub        4   4   2  	
009E  DC400001           [28] call       3   2   1  	
-- this is just at the end of every function
00A2  1E008000           [29] return     0   1 

Seems the same, although I just used string.dump in studio which might output something different than what’s actually used, or what happens isn’t visible in the bytecode.

I doubt it’s the bytecode that’s doing something, seems more like it’s another VM thing. It seems like roblox is internally doing something at the end of every line (not instruction/statement), unless the newline is in the middle of a statement or so:

do
	local start=tick()
	for i=1,1000000 do local a = i local b = i end
	print(tick()-start)
end

do
	local start = tick()
	for i = 1,1000000 do
		local a = i local b = i
	end
	print(tick() - start)
end

do
	local start = tick()
	for i = 1,1000000 do
		local a = i
		local b = i
	end
	print(tick() - start)
end

do
	local start = tick()
	for i = 1,1000000 do
		local a = i
		local b
		=
		i
	end
	print(tick() - start)
end
0.092844247817993
0.16371250152588
0.21370482444763
0.21733283996582

Roblox has done a lot to their VM, so good luck figuring out what’s really going on.

The time difference doesn’t happen on the commandline, so it’s probably something similar to Code speed inconsistency

4 Likes

Er, this seems completely random. But like @einsteinK did mention, Lua is compiled.

However, to clear some things up:
Lua is not “compiled on the go”, it’s made into a proto, and that’s what’s executed. Or in the case of server->client, proto to bytecode back to proto. There is no difference in the bytecode of each besides the lineinfo debug data, which could very well be the issue when being checked, but I doubt it.

Anyhow, here are my theories as to why this happens;

  • An inconsistency in the Roblox VM
  • Different usage of registers
  • lineinfo acting weird

I doubt Studio does this, but I know they also encrypt their instructions to an extent, which may slow things down.

TL;DR whitespace does not speed up code, and you shouldn’t abuse the lack of it

1 Like

Actually this thing and the “script __index” Code speed inconsistency are most likely one and the same. We assumed the latter happens every instruction, but it might be every line (that isn’t in the middle of a statement), which is quite easy to do with debug.sethook, but the internal C-sided version:


debug.sethook(print,'l')

local a=1

local b
=
1
line 4
line 8

Why would lua be checking every line / instruction? Isn’t everything that you run in a script going to be at the same security level?