Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Sandbox/User:Waddie96/benchmark

From Wikipedia, the free encyclopedia

-- __NOINDEX__
--- Lua benchmarks: Compare performance of two functions over CPU time
--- [[Module:Benchmark]]

local ITERATIONS = 1e6 -- Default number of iterations
local p = {}
local _format = string.format
local html_create = mw.html.create
local _sub = string.sub
local _byte = string.byte
local _hash = mw.hash.hashValue
local _tostring = tostring


--[[ local input = '          "nowiki", ">", "D", "</", "nowiki", ">  |<", "pre", ">", "E|F", "</",      '
local whitespace = { [" "] = 1, ["\n"] = 1, ["\t"] = 1, ["\r"] = 1 }
local strlen = #input
local function func1()
	-- local out = string.gsub(input, "^%s*(.-)%s*$", "%1")
	local lowEnd
	for i = 1, strlen do
		if not whitespace[_sub(input, i, i)] then
			lowEnd = i
			break
		end
	end
	if not lowEnd then
		return ""
	end
	for i = strlen, 1, -1 do
		if not whitespace[_sub(input, i, i)] then
			local out = _sub(input, lowEnd, i)
			return out
		end
	end
	return out
end

local function func2()
	local low, high = 1, strlen

	-- find first non-whitespace
	while low <= strlen and whitespace[_byte(input, low)] do
		low = low + 1
	end

	-- if all whitespace
	if low > strlen then return "" end

	-- find last non-whitespace
	while high >= low and whitespace[_byte(input, high)] do
		high = high - 1
	end

	return _sub(input, low, high)
end ]]

local to_number = tonumber
local _type = type
local _lower = string.lower

local function func1(val)
    -- If your wiki uses non-ascii characters for any of "yes", "no", etc., you
	-- should replace "val:lower()" with "mw.ustring.lower(val)" in the
	-- following line.
	val = _type(val) == 'string' and _lower(val) or val
	if val == nil then
		return nil
	elseif val == true
		or val == 'yes'
		or val == 'y'
		or val == 'true'
		or val == 't'
		or val == 'on'
		or to_number(val) == 1
	then
		return true
	elseif val == false
		or val == 'no'
		or val == 'n'
		or val == 'false'
		or val == 'f'
		or val == 'off'
		or to_number(val) == 0
	then
		return false
	else
		return default
	end
end

local LOWER = string.lower
local TO_NUMBER = tonumber
local TYPE = type
local BOOLEAN_MAP = {
	yes = true,
	y = true,
	["true"] = true,
	t = true,
	on = true,
	["1"] = true,
	no = false,
	n = false,
	["false"] = false,
	f = false,
	off = false,
	["0"] = false,
}
    
local function func2(value)
    if value == nil then
        return nil
    end

    local valueType = TYPE(value)

    if valueType == 'boolean' then
        return value
    elseif valueType == 'string' then
        local lookupResult = BOOLEAN_MAP[LOWER(value)]
        if lookupResult ~= nil then
            return lookupResult
        end
    end

    -- Numeric check works for both numbers and numeric strings.
    local number = TO_NUMBER(value)
    if number == 1 then
        return true
    elseif number == 0 then
        return false
    end

    return defaultResponse
end

--- Format seconds into readable text
--- @param seconds number
--- @return string
local function format_time(seconds)
	if seconds < 0.001 then
		-- less than 1 millisecond → show in microseconds
		return _format("%.3f µs", seconds * 1e6)
	elseif seconds < 0.1 then
		-- between 1 ms and 100 ms → show in milliseconds
		return _format("%.3f ms", seconds * 1e3)
	else
		-- otherwise show seconds
		return _format("%.6f s", seconds)
	end
end

--- Run a function multiple times and measure total CPU time
--- @param iterations integer Number of iterations
--- @return number elapsed1 CPU time in seconds
--- @return number elapsed2 CPU time in seconds
--- @return integer correct
--- @return integer incorrect
local function measure(iterations)
    local out1, out2 = {}, {}
    local correct, incorrect = 0, 0
	local start1 = os.clock()
	for _ = 1, iterations do
		table.insert(out1, _hash('xxh64', _tostring(func1(1))))
	end
	local stop1 = os.clock()
	local start2 = os.clock()
	for _ = 1, iterations do
		table.insert(out2, _hash('xxh64', _tostring(func2(1))))
	end
	local stop2 = os.clock()
    for i = 1, iterations do
        if out1[i] == out2[i] and out1[i] ~= nil and out2[i] ~= nil then
            correct = correct + 1
        else
            incorrect = incorrect + 1
        end
    end
	return (stop1 - start1), (stop2 - start2), correct, incorrect, out1[1], out2[1]
end

local function make_table(t1, t2, n, faster, percent, correct, incorrect, out1, out2)
    local num = n
    n = mw.getCurrentFrame():callParserFunction( "formatnum", n )
	local tbl = html_create("table")
		:addClass("wikitable")
		:cssText("text-align: center;")
		:node(
			html_create("tr")
				:node(html_create("th"):wikitext("Function"))
				:done()
				:node(html_create("th"):wikitext("Total time"))
				:done()
				:node(html_create("th"):wikitext("Average time per iteration"))
				:done()
		)
		:done()
		:node(
			html_create("tr")
				:node(html_create("td"):wikitext("Function 1"))
				:done()
				:node(html_create("td"):wikitext(format_time(t1)))
				:done()
				:node(html_create("td"):wikitext(format_time(t1 / num)))
				:done()
		)
		:done()
		:node(
			html_create("tr")
				:node(html_create("td"):wikitext("Function 2"))
				:done()
				:node(html_create("td"):wikitext(format_time(t2)))
				:done()
				:node(html_create("td"):wikitext(format_time(t2 / num)))
				:done()
		)
		:done()
		:node(
			html_create("tr")
                :node(
                    html_create("td"):cssText("font-weight: bold;"):wikitext('Comparison')
                )
				:done()
				:node(
					html_create("td"):attr("colspan", "2"):wikitext(
                        _format("Function %s is <strong>%.2f%% faster</strong>", faster, percent)
                    )
                )
				:done()
		)
		:done()
		:node(
			html_create("tr")
                :node(
                    html_create("td"):cssText("font-weight: bold;"):wikitext('Iterations')
                )
				:done()
				:node(
					html_create("td"):attr("colspan", "2"):wikitext(
                        mw.getCurrentFrame()
                            :callParserFunction( "formatnum", n )
                    )
                )
				:done()
		)
		:done()
        :node(
			html_create("tr")
                :node(
                    html_create("td"):cssText("font-weight: bold;"):wikitext("Correct")
                )
				:done()
				:node(
					html_create("td"):attr("colspan", "2"):wikitext(
                        mw.getCurrentFrame()
                            :callParserFunction( "formatnum", correct )
                    )
                )
				:done()
		)
		:done()
        :node(
			html_create("tr")
                :node(
                    html_create("td"):cssText("font-weight: bold;"):wikitext('Incorrect')
                )
				:done()
				:node(
					html_create("td"):attr("colspan", "2"):wikitext(
                        mw.getCurrentFrame()
                            :callParserFunction( "formatnum", incorrect )
                    )
                )
				:done()
		)
		:done()
        :allDone()
    out1 = out1 or 'nil'
    out2 = out2 or 'nil'
	tbl_str = mw.allToString(tbl) .. '<strong>Output 1:</strong> ' .. func1() ..
                                     '<br/> <strong>Output 2:</strong> ' .. func2() ..
                                     '<br/> <strong>Output 1:</strong>'  .. _tostring(out1) ..
                                     '<br/> <strong>Output 2:</strong>'  .. _tostring(out2)
	return tbl_str
end

--- Main benchmark function
--- @param iterations? integer Number of iterations (default 10000)
--- @return string Wikitext table comparing results
function p.run(iterations)
    local totalstart = os.clock()
	local n = tonumber(iterations) or ITERATIONS
	local t1, t2, correct, incorrect, out1, out2 = measure(n)
	local faster = t1 < t2 and "1" or "2"
	local diff = math.abs(t1 - t2)
	local percent = (diff / math.max(t1, t2)) * 100

	local out = make_table(t1, t2, n, faster, percent, correct, incorrect, out1, out2)
    local totalend = os.clock()
    local totalsec = totalend - totalstart
    out = out .. '<br/> <strong>Total time taken:</strong> ' .. format_time(totalsec)
    return out
end

return p