local B = {} local stat = require "plenary.benchmark.stat" local get_stats = function(results) local ret = {} ret.max, ret.min = stat.maxmin(results) ret.mean = stat.mean(results) ret.median = stat.median(results) ret.std = stat.std_dev(results) return ret end local get_output = function(index, res, runs) -- divine with a sutable one / 1e3, 1e6, 1e9 local time_types = { "ns", "μs", "ms" } local get_leading = function(time) time = math.floor(time) local count = 0 repeat time = math.floor(time / 10) count = count + 1 until time <= 0 return count end local get_best_fmt = function(time) for _, v in ipairs(time_types) do if math.abs(time) < 1000.0 then return string.format("%s%3.1f %s", string.rep(" ", 3 - get_leading(time)), time, v) end time = time / 1000.0 end return string.format("%.1f %s", time, "s") end return string.format( "Benchmark #%d: '%s'\n Time(mean ± σ): %s ± %s\n Range(min … max): %s … %s %d runs\n", index, res.name, get_best_fmt(res.stats.mean), get_best_fmt(res.stats.std), get_best_fmt(res.stats.min), get_best_fmt(res.stats.max), runs ) end local get_summary = function(res) if #res == 1 then return "" end local fastest_mean = math.huge local fastest_index = 1 for i, benchmark in ipairs(res) do if benchmark.stats.mean < fastest_mean then fastest_mean = benchmark.stats.mean fastest_index = i end end if fastest_mean == math.huge then return "" end local output = {} local fastest = res[fastest_index].stats for i, benchmark in ipairs(res) do if i ~= fastest_index then local result = benchmark.stats local ratio = result.mean / fastest.mean -- // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas -- // Covariance asssumed to be 0, i.e. variables are assumed to be independent local ratio_std = ratio * math.sqrt(math.pow(result.std / result.mean, 2) + math.pow(fastest.std / fastest.mean, 2)) table.insert(output, string.format(" %.1f ± %.1f times faster than '%s'\n", ratio, ratio_std, benchmark.name)) end end return string.format("Summary\n '%s' ran\n%s", res[fastest_index].name, table.concat(output, "")) end ---@class benchmark_run_opts ---@field warmup number @number of initial runs before starting to track time. ---@field runs number @number of runs to make ---@field fun table> @functions to execute ---Benchmark a function ---@param name string @benchmark name ---@param opts benchmark_run_opts local bench = function(name, opts) vim.validate { opts = { opts, "table" }, fun = { opts.fun, "table" }, } opts.warmup = vim.F.if_nil(opts.warmup, 3) opts.runs = vim.F.if_nil(opts.runs, 5) opts.fun = type(opts.fun) == "function" and { opts.fun } or opts.fun local output = { string.format("Benchmark Group: '%s' -----------------------\n", name) } local res = {} for i, fun in ipairs(opts.fun) do res[i] = { name = fun[1], results = {} } for _ = 1, opts.warmup do fun[2]() end for j = 1, opts.runs do local start = vim.loop.hrtime() fun[2]() res[i].results[j] = vim.loop.hrtime() - start end res[i].stats = get_stats(res[i].results) table.insert(output, get_output(i, res[i], opts.runs)) end print(string.format("%s\n%s", table.concat(output, ""), get_summary(res))) return res end return bench