Benchmark Module

lua-users home
wiki

Now, here is my benchmarking module. I wrote it to find out, which is the fastest way to do some things in Lua. It requires "HiResTimer".

Benchmark.lua

---------------------------
--- Benchmark provides a set of functions, which measures and compares execution time of different code fragments
---------------------------

module("Benchmark",package.seeall)
local hrt=require"HiResTimer"


local bench_results={}
local total_diff=0.0

---------------------------
--- Call a function for several times and measure execution time.
-- @param name (string) literal to identify test in result table
-- @param func (function) function to be benchmarked
-- @param loops (number) how often this function should be called
----------------------------
function Bench(name,func,loops)
	loops=loops or 1000
	local q0=hrt.clock()
	for i=1,loops do
		func()
	end
	local q1=hrt.clock()
	local result=bench_results[name] or {count=0,diff=0}
	local diff=(q1-q0)
	result.count=result.count+loops
	result.diff=result.diff+diff
	total_diff=total_diff+diff
	bench_results[name]=result
end

---------------------------
--- Do Benchmark over a table of functions
-- @param functions (table) table of functions to check
-- @param loops (number) how often to call the function (optional, default 1000)
---------------------------
function BenchTable(functions,loops)
	loops=loops or 1000
	for name,func in pairs(functions) do
		Bench(name,func,loops)
	end
end

----------------------------
--- Printout benchmark results.
-- @param Output (function) to receive values (optional, default=io.write)
----------------------------
function Results(Output)

	--
	-- prepare the output
	--
	Output=Output or io.write
	local function printf(form,...)
		Output(string.format(form,...))
	end

	--
	-- calculate mean values
	-- create a table of names
	--
	local names={}
	local namlen=0
	for name,result in pairs(bench_results) do
		result.mean=result.diff/result.count
--~ 		printf("diff=%8.3g cnt=%d mean=%8.3g\n",result.diff,result.count,result.mean)
		names[#names+1]=name
		if #name>namlen then namlen=#name end
	end

	--
	-- sort table by mean value
	--
	local function comp(na,nb)
		return bench_results[na].mean<bench_results[nb].mean
	end
	table.sort(names,comp)

	--
	-- derive some reasonable output scaling
	--
	local max=bench_results[names[#names]].mean
	local fac,unit=1,"sec"
	if max<0.001 then
		fac,unit=1000000,"µs"
	elseif max<1.0 then
		fac,unit=1000,"ms"
	end

	--
	-- create a format string (due "%-*s" is missing in string.format)
	--
	local form=string.gsub("-- %-#s : %8.3f %s = %6d loops/s [%6.2f %%] %5.3f times faster\n","#",tostring(namlen))

	--
	-- now print the result
	--
	printf("-----------------------------------\n")
	printf("-- MAX = %8.3f %s\n",max*fac,unit)
	for i=1,#names do
		local name=names[i]
		result=bench_results[name]
		local ratio=result.mean*100/max
		local times=max/result.mean
		local loops=1/result.mean
		printf(form,name,result.mean*fac,unit,loops,ratio,times)
	end
	printf("-----------------------------------\n")
end

return Benchmark

Example : string concatenation

The question is, how to combine some literals and some variables to a string.

require"Benchmark"

local sf=string.format

local TestCases=
{

	TextConcat=function()
		local t,a,s='A','#',5
		local n=""
		for i=1,1000 do
			n="\symbol{circled"..s.."}="..t..a..i
		end
	end,

	-- extreme slow
	TableConcat=function()
		local t,a,s='A','#',5
		local n=""
		for i=1,1000 do
			n=table.concat{"\symbol{circled",s,"}=",t,a,i}
		end
	end,

	StringFormat=function ()
		local t,a,s='A','#',5
		local n=""
		for i=1,1000 do
			n=string.format("\symbol{circled%d}=%s%s%d",s,t,a,i)
		end
	end,

	FunctionLocalStringFormat=function()
		local t,a,s='A','#',5
		local n=""
		local sf=string.format
		for i=1,1000 do
			n=sf("\symbol{circled%d}=%s%s%d",s,t,a,i)
		end
	end,

	ModuleLocalStringFormat=function()
		local t,a,s='A','#',5
		local n=""
		for i=1,1000 do
			n=sf("\symbol{circled%d}=%s%s%d",s,t,a,i)
		end
	end
}
Benchmark.BenchTable(TestCases,100)
Benchmark.Results()

Result

surprisingly string.format does it best. Localizing it gives just a little speedup then.
-----------------------------------
-- MAX =    4.951 ms
-- ModuleLocalStringFormat   :    1.665 ms =    600 loops/s [ 33.63 %] 2.974 times faster
-- FunctionLocalStringFormat :    1.696 ms =    589 loops/s [ 34.26 %] 2.919 times faster
-- StringFormat              :    1.728 ms =    578 loops/s [ 34.90 %] 2.865 times faster
-- TextConcat                :    2.754 ms =    363 loops/s [ 55.64 %] 1.797 times faster
-- TableConcat               :    4.951 ms =    201 loops/s [100.00 %] 1.000 times faster
-----------------------------------

Example loop over array

Often diskussed for i=1,#array vs. for i,v in ipairs

-----------------------------------
-- setup test data
-----------------------------------

local array={}

for i=1,100 do
	local x={}
	for j=1,100 do
		x[j]=tostring(i)..tostring(j)
	end
	array[i]=x
end

local TestCases=
{
	for_i_array=function()
		local count=0
		for i=1,#array do
			local x=array[i]
			for j=1,#x do
				local y=x[j]
				-- do something with y
			end
		end
		return count
	end,

	for_ipairs=function()
		local count=0
		for i,x in ipairs(array) do
			for j,y in ipairs(x) do
				-- do something with y
			end
		end
		return count
	end
}

local Benchmark=require"Benchmark"
Benchmark.BenchTable(TestCases,1000)
Benchmark.Results()

Result

for i=1,#array is the winner
-----------------------------------
-- MAX =    1.127 ms
-- for_i_array :    0.689 ms =   1451 loops/s [ 61.12 %] 1.636 times faster
-- for_ipairs  :    1.127 ms =    887 loops/s [100.00 %] 1.000 times faster
-----------------------------------

RecentChanges · preferences
edit · history
Last edited August 30, 2010 4:33 pm GMT (diff)