diff --git a/init.lua b/init.lua index 6819aca..2acf119 100644 --- a/init.lua +++ b/init.lua @@ -39,6 +39,7 @@ local _batteries = { manual_gc = require_relative("manual_gc"), colour = require_relative("colour"), pretty = require_relative("pretty"), + measure = require_relative("measure"), make_pooled = require_relative("make_pooled"), } diff --git a/measure.lua b/measure.lua new file mode 100644 index 0000000..15eeff5 --- /dev/null +++ b/measure.lua @@ -0,0 +1,90 @@ +--[[ + very simple benchmarking tools for finding out + how long something takes to run +]] + +local path = (...):gsub("measure", "") +local functional = require(path .. "functional") + +local measure = {} + +--replace this with whatever your highest accuracy timer is +--os.time will almost certainly be too coarse +measure.get_time = os.time +if love and love.timer then + --love.timer is _much_ better + measure.get_time = love.timer.getTime +end + +--measure the mean, minimum, and maximum time taken in seconds to run test_function +--over several runs (default 1000). +--warmup_runs can be provided to give the JIT or cache some time to warm up, but +--are off by default +function measure.time_taken(test_function, runs, warmup_runs) + --defaults + runs = runs or 1000 + warmup_runs = warmup_runs or 0 + --collect data + local times = {} + for i = 1, warmup_runs + runs do + local start_time = measure.get_time() + test_function() + local end_time = measure.get_time() + if i > warmup_runs then + table.insert(times, end_time - start_time) + end + end + + local mean = functional.mean(times) + local min, max = functional.minmax(times) + return mean, min, max +end + +--measure the mean, minimum, and maximum memory increase in kilobytes for a run of test_function +--doesn't modify the gc state each run, to emulate normal running conditions +function measure.memory_taken(test_function, runs, warmup_runs) + --defaults + runs = runs or 1000 + warmup_runs = warmup_runs or 0 + --collect data + local mems = {} + for i = 1, warmup_runs + runs do + local start_mem = collectgarbage("count") + test_function() + local end_mem = collectgarbage("count") + if i > warmup_runs then + table.insert(mems, math.max(0, end_mem - start_mem)) + end + end + + local mean = functional.mean(mems) + local min, max = functional.minmax(mems) + return mean, min, max +end + +--measure the mean, minimum, and maximum memory increase in kilobytes for a run of test_function +--performs a full collection each run and then stops the gc, so the amount reported is as close as possible to the total amount allocated each run +function measure.memory_taken_strict(test_function, runs, warmup_runs) + --defaults + runs = runs or 1000 + warmup_runs = warmup_runs or 0 + --collect data + local mems = {} + for i = 1, warmup_runs + runs do + collectgarbage("collect") + collectgarbage("stop") + local start_mem = collectgarbage("count") + test_function() + local end_mem = collectgarbage("count") + if i > warmup_runs then + table.insert(mems, math.max(0, end_mem - start_mem)) + end + end + collectgarbage("restart") + + local mean = functional.mean(mems) + local min, max = functional.minmax(mems) + return mean, min, max +end + +return measure diff --git a/readme.md b/readme.md index b075c08..ff8282e 100644 --- a/readme.md +++ b/readme.md @@ -92,6 +92,7 @@ These modules are probably only useful to some folks in some circumstances, or a - [`async`](./async.lua) - Asynchronous/"Background" task management. - [`colour`](./colour.lua) - Colour conversion routines. Alias `color`. - [`manual_gc`](./manual_gc.lua) - Get GC out of your update/draw calls. Useful when trying to get accurate profiling information; moves "randomness" of GC. Requires you to think a bit about your garbage budgets though. +- [`measure`](./measure.lua) - Benchmarking helpers - measure the time or memory taken to run some code. - [`unique_mapping`](./unique_mapping.lua) - Generate a unique mapping from arbitrary lua values to numeric keys - essentially making up a consistent ordering for unordered data. Niche, but can be used to optimise draw batches for example, as you can't sort on textures without it. - [`make_pooled`](./make_pooled.lua) - add pooling/recycling capability to a class