Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Algorithmic optimizations on math.ease functions #2198

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

RubisetCie
Copy link
Contributor

Good day.

Because my addons rely heavily on math.ease functions (which is an awesome addition for the glua, btw), I took the responsibility to optimize some of its expressions.

Mainly, it is about converting the ^ operator to a succession of multiplications *; it is faster because exponentiation in Lua relies on the pow C function, which is too much overhead for simple squaring, cubing, etc.

So, x ^ 2 becomes x * x, and so on...

  • I've made some benchmarks on my end, and the performance gain is compelling (at best, 200% faster, never slower).
  • I've made sure the output value for a same input stayed the same for each modified function, it's a 100% match, so no compatibility break.

@robotboy655 robotboy655 added the Enhancement The pull request enhances current functionality. label Jan 29, 2025
@robotboy655
Copy link
Collaborator

I'd like to see the benchmarks you have performed. I am having trouble seeing a consistent improvement, or any at all using the example from wiki:


function math.ease.InOutQuad2( x )
	if ( x < 0.5 ) then
		return 2 * ( x * x )
	else
		local w = -2 * x + 2
		return 1 - ( w * w ) / 2
	end
end

function math.ease.InCubic2( x )
return x * x * x
end

local count = 3000000000

local StartTime = SysTime()
local oldFUnc =math.ease.InCubic
local newFUnc =math.ease.InCubic2
for i = 1, count do
	-- Repeat an action 10,000 times to check how long it takes on average
	-- Example action:
	newFUnc( ( i % 100 ) / 100 )
end

local EndTime = SysTime()
local TotalTime = EndTime - StartTime

print("TotalNew : " .. TotalTime .. " seconds. " )




local StartTime = SysTime()

for i = 1, count do
	-- Repeat an action 10,000 times to check how long it takes on average
	-- Example action:
	oldFUnc( ( i % 100 ) / 100 )
end
 
local EndTime = SysTime()
local TotalTime = EndTime - StartTime

print("TotalOld: " .. TotalTime .. " seconds. " )

image

Sometimes the proposed changes are slower than the current code in these results.

@RubisetCie
Copy link
Contributor Author

RubisetCie commented Jan 29, 2025

Sure, I will post the benchmarking code tomorrow.

But it was kinda similar to yours, @robotboy655, so I'm not sure on why my results were drastically different...

In the meantime, here are my (low-end) specs on which I performed my test: :)

  • OS: Linux (the distro doesn't matter I guess)
  • GMod branch: Stable
  • CPU: Intel Core i5-1235U

What version of Lua does Garry's Mod uses ? 5.3 ? 5.4 ?

@robotboy655
Copy link
Collaborator

robotboy655 commented Jan 29, 2025

GMod uses LuaJIT, which is Lua 5.1.

I am testing on a fairly good machine with a Ryzen 5900x, on Windows 10.

@goodusername123
Copy link

goodusername123 commented Jan 30, 2025

Runtime performance between PUC Lua 5.1 (or really any version of PUC Lua) and LuaJIT is wildly different, especially on the math side of things since in LuaJIT most of that stuff (including pow) gets optimized into machine instructions and even when the relevant code that's being run doesn't make it through the JIT most of the math operations themselves are implemented in assembly rather then C.

Any benchmarking that's done NEEDS to be done with/in LuaJIT or the results won't matter, better yet benchmarking should probably be done inside GMod itself as done in a prior comment on this PR if possible. @RubisetCie

@RubisetCie
Copy link
Contributor Author

RubisetCie commented Jan 30, 2025

OK, here are the scripts I made to do my benchmarking:

easing-benchmark.zip

  • bench1.lua: Measures the time of each easing function before my change.
  • bench2.lua: Measures the time of each easing function after my change.
  • results-original.txt: Reports the results of bench1 (before the change).
  • results-new.txt: Reports the results of bench2 (after the change).

It may be naive, but I did another test this morning using LuaJit, and the results were like even more drastic... especially for InCubic, where I get an improvement so big I suspect there may be an error... so I'm curious about your analysis. :)

@RubisetCie
Copy link
Contributor Author

in LuaJIT most of that stuff (including pow) gets optimized into machine instructions and even when the relevant code that's being run doesn't make it through the JIT most of the math operations themselves are implemented in assembly rather then C.

This is true. That could explain the lack of improvements if the benchmarks are refuted.

Any benchmarking that's done NEEDS to be done with/in LuaJIT or the results won't matter, better yet benchmarking should probably be done inside GMod itself as done in a prior comment on this PR if possible. @RubisetCie

LuaJit is done, and shows improvements. I'll test in GMod right now.

@RubisetCie
Copy link
Contributor Author

And indeed, the benchmarking in Garry's Mod shows a different story, for some reason:

gm_construct0000

Here the timings are equivalents. If this is true, abort this pull request.

@goodusername123
Copy link

After doing a bunch of benchmarking and testing and digging (I ended up testing 12 different compiles of LuaJIT) I have found out and concluded that this behavior is LuaJIT version dependent.
more specifically LuaJIT performing worse with the current ^ operator math is due to pow related changes that happened to LuaJIT in 2022 (I originally thought it was due to changes in 2020 but testing revealed I was wrong)
however the reason this doesn't happen in GMod (both branches) is because it's on an older version of LuaJIT (main is on 2.0.4 and x86-64 is on 2.1.0beta3 from 2017)

If GMod ever updates it's copy of LuaJIT to a modern version (hopefully it will one day) then this pull request would actually be useful if the performance of pow in modern LuaJIT stays as the slow behavior introduced in 2022, However current day Gmod with it's old copy won't really see much of an improvement or regression with this pull request.

LuaJIT POW benchmark results.rar.zip

related LuaJIT commits/issues:
LuaJIT/LuaJIT#684 (comment) (LuaJIT/LuaJIT@9512d5c)
LuaJIT/LuaJIT#817 (LuaJIT/LuaJIT@96d6d50 LuaJIT/LuaJIT@8b8304f LuaJIT/LuaJIT@43ebb94)

@RubisetCie
Copy link
Contributor Author

Thank you for this deep analysis, @goodusername123 :)
It answers all of my questions about benchmark differences.

So, I guess it's better to merge my PR, but I'm bothered by the current Garry's Mod benchmarks, which showed the new version being sometimes slower than the old one... even if there's a confounding factor, I'm troubled. :|

@HEV-Scientist
Copy link

I've noticed You've increased MAX_MAP_BRUSHES from 16384 to 65535, does this mean really big maps is possible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement The pull request enhances current functionality.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants