Benchmarker Plugin - Compare function speeds with graphs, percentiles, and more!

Update!

V 6.1


  • Edge Case Fixes

Thank you to @AstroCode for bringing these issues to my attention.

If you ran a really slow function for a Run Time that would only have enough time to call it a couple times, it would cause errors and funky behavior. It would attempt to process data that simply wasn’t captured, and the graph would be trying to draw data increments that lead to weird valleys.
Regardless of errors/misbehavior, having so few data points is useless because it’s not enough info to have accurate and reliable benchmark results.

To solve this, I made it error if you have too few data points, and it estimates how much time you’d need to run the bench for in order to get enough data points. I also made the graph drawing handle small datasets more intelligently.

image

4 Likes

I have no clue if this is a problem on my end on studio, but whenever I enable the benchmarker plugin, my mouse seems to invert rather than being at a normal state, usually after I enable / disable it.

Repo:

  • Open a new place or file.
  • Open the Benchmarker plugin (click it once or twice if you have it at start-up).
  • ig check the mouse:

Restarting the application or studio didn’t do the trick.

Roblox has been having issues with plugin widgets and the cursor lately. They’ve been reported already, so we just have to sit tight.

1 Like

Update!

V 6.2


  • Fool Proofing

With a complicated tool like this, it’s easy to do things wrong if you don’t understand it. I’ve noticed people misusing the plugin lately, so this update is an attempt at catching common mistakes before they happen. I’ve added protections against common mistakes like benching yielding functions or running sub-microsecond tests.
If you’ve made a mistake that the plugin could have caught for you, DM me what it was and I’ll try to incorporate some checks against that.

image
image

Funny 'feature'

If you run a blank function, it runs so fast that it’ll suggest you run it inf times
image image

4 Likes

Lol sorry im dumb. It was accident. Good work though…

I’ve recently gathered quite the collection of benchmark files and with that, it’s become inconvenient to manually display each bench.

It would be cool to have a feature that gathers and executes all of your selected bench files. With that, there should be a way to easily select the result of a specific bench. Adopting a design similar to nexus unit testing, would be beautiful and offer a ton of possibility. On each tab in the list, it could display the file path, then it drops down to each benchmark and a gist of the results. Clicking on one of the tabs (or specific benchmark) would open the graph and show all that fancy stuff.

3 Likes

Every time I insert a benchmark with the “Create Bench” button, I remove all of the comments and extra code because it is overwhelming and distracting. Would you consider adding a barebones .bench to the Library? Something like

return {
	ParameterGenerator = function()
		return
	end,

	Functions = {
		["Sample A"] = function(Profiler, Parameter)

		end,

		["Sample B"] = function(Profiler, Parameter)

		end
	}
}
2 Likes

Sort of a bump, but ditto to this nevertheless. I end up just copying over a barebone modulescript across places.

1 Like

Hey there,

I have done what the plugin asks, i have added a loop onto the function and the error won’t go away, any tips?

Here is the code:

--[[

|WARNING| THESE TESTS RUN IN YOUR REAL ENVIRONMENT. |WARNING|

If your tests alter a DataStore, it will actually alter your DataStore.

This is useful in allowing your tests to move Parts around in the workspace or something,
but with great power comes great responsibility. Don't mess up your stuff!

---------------------------------------------------------------------

Documentation and Change Log:
https://devforum.roblox.com/t/benchmarker-plugin-compare-function-speeds-with-graphs-percentiles-and-more/829912/1

--------------------------------------------------------------------]]

return {

	ParameterGenerator = function()
		-- This function is called before running your function (outside the timer)
		-- and the return(s) are passed into your function arguments (after the Profiler). This sample
		-- will pass the function a random number, but you can make it pass
		-- arrays, Vector3s, or anything else you want to test your function on.
		return math.random(1000)/10
	end;

	Functions = {
		["table create()"] = function(Profiler, RandomNumber) -- You can change "Sample A" to a descriptive name for your function
			for _ = 1,1000 do
				wait(1)
				local t = table.create(4000)
				for i = 1,4000 do
					wait()
					t[i] = RandomNumber
				end
			end

		end;

		["Sample B"] = function(Profiler, RandomNumber)
			for _ = 1,1000 do
				wait(1)
				local t = {}
				for i = 1,4000 do
					wait()
					t[i] = RandomNumber
				end
			end
		end;

		-- You can add as many functions as you like!
	};

}

You cannot put a wait in a speed test, that ruins the entire purpose.

3 Likes

Update: Quality of Life Changes

  • Improved the coloring system to avoid conflicting colors and more evenly spread across the color space. This adapts to both light and dark theme.
    Before (Multiple purple hues) | After (Four distinct colors)


    (Thank you to @MrGreystone for the new color algorithm)

  • Added the ability to drag resize the profiler/gap bounds. It was previously annoying to read many stacked profiler labels since there wasn’t enough space, now you can make it larger while you read.
    dragging

9 Likes

Is there any way that you could implement assert()? I was trying to benchmark assert() vs

local function MaybeQuickAssert(Condition, ErrorMessage)
   if not Condition then
   	print(ErrorMessage) -- Plugin thinks warn() is the code erroring so we use
   	return false -- print() and return which together should have a similar footprint
   end
end

and when passed a condition that caused it to alert the user then it gives an error and doesn’t benchmark. I’m guessing probably not without a custom function that would make the benchmark redundant since it doesn’t use the same code and therefore would take a different amount of time. Although, a custom function with an attached time that replaces the time it takes for the function to execute with the time it takes assert() to execute could work although it’s likely more trouble than it’s worth since the time it takes for a function to run depends on the specs of the user. Anyway, I was bored so I made this as a kind of proof of concept (minus the tricky bit lol).

-- Inside a ModuleScript
local TimerModule = {}

function TimerModule.new()
	local Timer = {
		__PauseStartTime = 0,
		StartTime = 0,
		PauseLength = 0,
		EndTime = 0,
		RunTime = 0
	}

	function Timer:Start()
		self.StartTime = os.clock() 
	end

	function Timer:Pause()
		self.__PauseStartTime = os.clock()
	end

	function Timer:Play()
		self.PauseLength += os.clock() - self.__PauseStartTime
		self.__PauseStartTime = 0
	end

	function Timer:AddTime(Time)
		self.RunTime += Time
	end

	function Timer:Stop()
		self.EndTime = os.clock()
		local RunTime = self.RunTime + (self.EndTime + self.PauseLength) - self.StartTime
		self.RunTime = RunTime
		return RunTime
	end

	function Timer:Destroy()
		self.__PauseStartTime = nil
		self.StartTime = nil
		self.PauseLength = nil
		self.EndTime = nil
		self.RunTime = nil
	end

	return Timer
end
return TimerModule


-- Inside a Script
-- Setup and other plugin stuff here
local Timer = TimerModule.new() 

-- Run button pressed here

local Code -- However you get the users code

if string.find(Code, " assert(") then  -- Space means it's not part of another function name and only one bracket since there would be arguments in between
	Timer:AddTime(1) -- Just a random number here for now, but it could possibly be based off the users framerate?
	Code = string.gsub(Code, " assert(", " FakeAssert(") -- And FakeAssert defined in the preset script (or maybe added by this bit of code?)
	Code = [[
local function FakeAssert(Condition, ErrorMessage)
	if not Condition then
		warn(ErrorMessage)
	end
end \n
]]..Code -- It's a bit of a mess but it *should* add the FakeAssert function to the top (also this bit and the gsub bit could be put in one for a performance enhancement)
end

Timer:Start()
-- Code is executed here

I just am curious as to what you specifically mean by this. I am not very skilled when it comes to statistics as of now, but I am pretty sure if you took the weighted average of a data set that had a good bit of size to it the results would not be severely skewed from a couple of outliers. Clarification on this would be great.

Let’s say you have 10 data points of function timings. 9 of them are 100 milliseconds exactly, but 1 happened to be skewed by a GC step or something and ended up at 800ms.

Average = ((100*9)+(800))/10 = 170ms
Fiftieth percentile = 100ms

The average is 70% increased over the real world expected performance thanks to a single outlier, while the fiftieth percentile is a much more meaningful and accurate value.

Regardless, I provide both values in this plugin.
With the plugin’s histogram, you get to view how your code really behaves, rather than just a single metric. This plugin gives you as much information as possible so you can make informed decisions.

1 Like

Icons looking a little blurry there. :wink:

image

Any chance to use 2x versions instead so that they aren’t blurry on HiDPI screens? Roblox Studio is getting HiDPI support soon (FFlagStudioWindowsDpiScale), which makes it SO much easier to use it on 4K screens.

Also, the main benchmarking window:

This… doesn’t… look… right? The points & lines are extremely thick, is that intentional? Plus the text is obscured?

It looks in your screenshots like you’re using the plugins on a HiDPI screen without the FFlag enabled, so things look much smaller to you. Have you tested the plugin on smaller screens (mine is effectively 1080p, just with sharper details)? Maybe enable that FFlag and see what it looks like at the correct scale?

P.S. Try using UIStroke instead of text stroke, it behaves properly in HiDPI.

HiDPI has broken so much of my stuff aaaaAAAAA

Thank you for reporting!

2 Likes

Lol! I donated $5 to your plugin on Itch because I know how useful it’s going to be for me. It’s REALLY nice, and I mean REALLY nice.

1 Like

What you could do with the dots is size them according to the panel width.

Make it so that on the very bottom, there is some line between the dots… like this, but if the dots were like half the size, lol.

Or just add a setting for it.

And you could size the lines to be 1/4 or 1/3 of the dot size.

Edit: image

Also looks like there are some scaling artifacts on these, you should either make sure to PixelFix your circle, or use UICorner…

2 Likes

Feature request: Add an option for Benchmarker to yield every X seconds, i.e. 0.1 or so, during benchmarking. This would allow me to run benchmarks for many minutes while I work on other things. Having it enabled could also add a Cancel button to the “Running benchmark…” screen.

Enable numbers of calls larger than 10k, but only when the yield option is enabled.

It would also give a bit more varied results when the functions being benchmarked include wildcards like Instance.new (which many of your library benchmarks do use, which is kind of a bad practice but whatever), although that’s not always desirable, hence the optionality.

1 Like

I really want to buy this plugin, but I didn’t before because via robux it was like really expensive and just wasn’t worth it for the shares that Roblox would get, but, now I’ve noticed it moved to a real purchase thing, but now I can’t buy it, because, itch will not use the debit function (and might not be able to do so as I’m not on the USA), and I can’t use credit or use PayPal (as I’m -18), my bank does have an option for buying things with credit but working just like debit, but it won’t work for international purchases, so it doesn’t work either. Do you know like any other method that I could use to purchase Benchmarker? (apart from Robux which isn’t even a thing right now)

7 Likes