diff --git a/benchmark/matobj/GAP-double-index-mat-access.g b/benchmark/matobj/GAP-double-index-mat-access.g new file mode 100644 index 00000000000..2ebd6ecdd19 --- /dev/null +++ b/benchmark/matobj/GAP-double-index-mat-access.g @@ -0,0 +1,72 @@ +# Two hacks + + +#InstallMethod( \[\], [ IsMatrix, IsList ], {m,l} -> m[l[1]][l[2]] ); +InstallOtherMethod( \[\], [ IsMatrix, IsPosInt, IsPosInt ], {m,i,j} -> m[i][j] ); +InstallOtherMethod( MatElm, [IsMatrix,IsPosInt,IsPosInt], {m,i,j} -> m[i][j] ); + + +m:=List([1..10],i->List([1..10],j-> (i-1)*10 + j)); + + +gap> m:=IdentityMat(10);; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i][j]; od; od; od; time; +62 +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i,j]; od; od; od; time; +596 +gap> for i in [1..10] do for j in [1..10] do Assert(0, m[i,j] = m[i][j]); od; od; + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MatElm(m,i,j); od; od; od; time; +525 +gap> f:=ApplicableMethod(MatElm, [m,i,j]);; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=f(m,i,j); od; od; od; time; +54 + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MATELM_PLIST(m,i,j); od; od; od; time; +54 +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MATELM_LIST(m,i,j); od; od; od; time; +63 + + +gap> m:=IdentityMat(10,GF(2));; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i][j]; od; od; od; time; +68 +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i,j]; od; od; od; time; +468 +gap> for i in [1..10] do for j in [1..10] do Assert(0, m[i,j] = m[i][j]); od; od; + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MatElm(m,i,j); od; od; od; time; +379 +gap> f:=ApplicableMethod(MatElm, [m,i,j]);; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=f(m,i,j); od; od; od; time; +135 + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MATELM_LIST(m,i,j); od; od; od; time; +81 + + +gap> m:=IdentityMat(10,GF(2));; ConvertToMatrixRep(m);; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i][j]; od; od; od; time; +152 # note: SLOWER +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=m[i,j]; od; od; od; time; +354 +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MatElm(m,i,j); od; od; od; time; +227 +gap> f:=ApplicableMethod(MatElm, [m,i,j]);; +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=f(m,i,j); od; od; od; time; +224 + + +gap> for i in [1..10] do for j in [1..10] do Assert(0, m[i,j] = m[i][j]); od; od; +gap> for i in [1..10] do for j in [1..10] do Assert(0, MAT_ELM_GF2MAT(m,i,j) = m[i][j]); od; od; + + +InstallOtherMethod( \[\], [ IsGF2MatrixRep, IsPosInt, IsPosInt ], MAT_ELM_GF2MAT ); +InstallMethod( MatElm, [ IsGF2MatrixRep, IsPosInt, IsPosInt ], MAT_ELM_GF2MAT ); + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MAT_ELM_GF2MAT(m,i,j); od; od; od; time; +60 +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MatElm(m,i,j); od; od; od; time; +65 + +gap> for u in [1..10000] do for i in [1..10] do for j in [1..10] do x:=MATELM_LIST(m,i,j); od; od; od; time; diff --git a/benchmark/matobj/bench-mat.g b/benchmark/matobj/bench-mat.g new file mode 100644 index 00000000000..e8624725d55 --- /dev/null +++ b/benchmark/matobj/bench-mat.g @@ -0,0 +1,202 @@ +ReadGapRoot("bench.g"); + +# TODO: the following helper functions are meant to allow writing +# code that uses (Set)MatElm but still accepts "old-style" matrices. +# as input. We might want to add them to the library... or maybe not, +# as new code probably should use mat[i,j] directly instaed. +InstallOtherMethod( MatElm, [ IsMatrix, IsPosInt, IsPosInt ], + -RankFilter(IsMatrix), + {m,i,j} -> m[i,j] ); +InstallOtherMethod( SetMatElm, [ IsMatrix, IsPosInt, IsPosInt, IsObject ], + -RankFilter(IsMatrix), + function(m,i,j,v) m[i,j]:=v; end); + +# TODO: add tests for all of this +# TODO: also test Unbind(mat[i,j]) / IsBound(mat[i,j]) ?!? then again, we might not want +# to support these at all, given that matrix objects should be dense +# TODO: also add tests for out-of-bounds array indices + + +PrintBoxed := function(str) + local n, line; + n := Length(str) + 2; + line := Concatenation("+", ListWithIdenticalEntries(n,'-'), "+\n"); + Print(line); + Print("| ", str, " |\n"); + Print(line); +end; + +PrintHeadline := function(what); + Print(TextAttr.5, "Testing ", what, ":\n",TextAttr.reset); +end; + +MyBench := function(func) + local opt, res; + + opt := rec( + mintime:=300, + showSummary := false + ); + res := Benchmark(func, opt); + + Print(" ", Round(res.counter * 1000.0 / res.total ), " iterations per second\n"); + +# Print(" Performed ", res.counter, " iterations, taking ", Round(res.total), " milliseconds; "); +# #Print("timings: ", timings, "\n"); +# # Print("average: ", Round(100*res.avg)/100, +# # " +/- ", Round(100*res.std)/100, +# # " (+/- ", Round(100*res.std/res.avg), "%)", +# # "\n"); +# Print(" median: ", res.median, "\n"); +end; + + +TestReadingMatrix := function(m) + local f; + + PrintHeadline("m[i][j]"); + MyBench(function() + local u, i, j, rows, cols, x; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + x:=m[i][j]; + od; + od; + od; + end); + + PrintHeadline("m[i,j]"); + MyBench(function() + local u, i, j, rows, cols, x; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + x:=m[i,j]; + od; + od; + od; + end); + + PrintHeadline("MatElm(m,i,j)"); + MyBench(function() + local u, i, j, rows, cols, x; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + x:=MatElm(m,i,j); + od; + od; + od; + end); + + PrintHeadline("MatElm(m,i,j) with prefetched method"); + f:=ApplicableMethod(MatElm, [m,1,1]);; + MyBench(function() + local u, i, j, rows, cols, x; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + x:=f(m,i,j); + od; + od; + od; + end); + +end; + +TestWritingMatrix := function(m) + local f; + + PrintHeadline("m[i][j]:=elm"); + MyBench(function() + local u, i, j, rows, cols, x; + x:=m[1][1]; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + m[i][j]:=x; + od; + od; + od; + end); + + PrintHeadline("m[i,j]:=elm"); + MyBench(function() + local u, i, j, rows, cols, x; + x:=m[1][1]; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + m[i,j]:=x; + od; + od; + od; + end); + + PrintHeadline("SetMatElm(m,i,j,elm)"); + MyBench(function() + local u, i, j, rows, cols, x; + x:=m[1][1]; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + SetMatElm(m,i,j,x); + od; + od; + od; + end); + + PrintHeadline("SetMatElm(m,i,j,elm) with prefetched method"); + f:=ApplicableMethod(SetMatElm, [m,1,1,m[1][1]]);; + MyBench(function() + local u, i, j, rows, cols, x; + x:=m[1][1]; + rows := [1..Length(m)]; + cols := [1..Length(m[1])]; + for u in [1..QuoInt(100000,Length(m)*Length(m[1]))] do + for i in rows do + for j in cols do + f(m,i,j,x); + od; + od; + od; + end); +end; + +RunMatTest := function(desc, m) + Print("\n"); + PrintBoxed(Concatenation("Testing ", desc)); + TestReadingMatrix(m); + Print(TextAttr.2, "...now testing write access...\n", TextAttr.reset); + TestWritingMatrix(m); +end; + +m:=IdentityMat(10);; +RunMatTest("integer matrix", m); + +m:=IdentityMat(10,GF(2));; +RunMatTest("GF(2) rowlist", m); + +m:=IdentityMat(10,GF(2));; ConvertToMatrixRep(m);; +RunMatTest("GF(2) compressed matrix", m); + +m:=IdentityMat(10,GF(7));; +RunMatTest("GF(7) rowlist", m); + +m:=IdentityMat(10,GF(7));; ConvertToMatrixRep(m);; +RunMatTest("GF(7) compressed matrix", m); diff --git a/benchmark/matobj/bench.g b/benchmark/matobj/bench.g new file mode 100644 index 00000000000..cd497e2c782 --- /dev/null +++ b/benchmark/matobj/bench.g @@ -0,0 +1,145 @@ +# Benchmark( func[, optrec] ) +# +# func - a function taking no arguments +# optrec - an optional record with various options +# +# Measures how long executing the given function "func" takes. +# In order to improve accuracy, it invokes the function repeatedly. +# Before each repetition, the garbage collector is run, and +# (unless turned off by an option) the random number generators +# are reset. +# At the end, it outputs the average, median, and std deviation. +# +# Example: +# gap> Benchmark(function() Factors(x^293-1); end); +# ................................................. +# Performed 49 iterations, taking 201 milliseconds. +# average: 4.1 +/- 0.11 (+/- 3%) +# median: 4.06396484375 +# rec( avg := 4.09581, counter := 49, median := 4.06396, std := 0.109638, total := 200.695, var := 0.0120205 ) +# +# +# The following options are currently implemented: +# +# minreps: the minimal number of repetitions; +# the function will be executed at least that often, +# unless some other condition (maxreps exceeded, maxtime exceeded) +# aborts the benchmark early. +# mintime: the minimal number of milliseconds that has to pass before +# benchmarking ends; +# benchmarking will not stop before this time has passed, +# unless some other condition (maxreps exceeded, maxtime exceeded) +# aborts the benchmark early. +# maxreps: the maximal number of repetitions; +# once this is reached, benchmarking stops immediately. +# maxtime: the maximal number of milliseconds before benchmarking ends; +# once this is reached, benchmarking stops immediately. +# showSummary: +# showProgress: +# resetRandom: whether to reset the random number generators before each repetition (default: true) +# +# +# TODO: allow passing a function that is executed before every test run. +# That function can reset other state, flush caches etc. +# +Benchmark := function(func, arg...) + local opt, getTime, timings, total, t, i, res; + + opt := rec( + minreps := 5, + maxreps := 1000, + silent := false, + resetRandom := true, + mintime := 200, + maxtime := infinity, + showProgress := true, + showSummary := true, + ); + + if Length(arg) = 1 and IsRecord(arg[1]) then + for i in RecNames(arg[1]) do + opt.(i) := arg[1].(i); + od; + elif Length(arg) <> 0 then + Error("Usage: Benchmark( func[, optrec] )"); + fi; + + # force mintime and maxtime to be floats + opt.mintime := Float(opt.mintime); + opt.maxtime := Float(opt.maxtime); + + # if available, use NanosecondsSinceEpoch + if IsBound(NanosecondsSinceEpoch) then + getTime := function() return NanosecondsSinceEpoch()/1000000.; end; + else + getTime := Runtime; + fi; + + timings := []; + total := 0.0; + i := 0; + # repeat at most opt.maxreps times, and at least opt.minreps + # times, resp. at least till opt.mintime milliseconds passed + # + # TODO: what we really should do is repeat until the variance + # is low enough (or until it stagnates) + while i < opt.maxreps and (opt.maxtime = infinity or total < opt.maxtime) and + (i < opt.minreps or total < opt.mintime) do + i := i + 1; + + if opt.resetRandom then + Reset(GlobalMersenneTwister); + Reset(GlobalRandomSource); + fi; + GASMAN("collect"); + + t := getTime(); + func(); + t := getTime() - t; + + total := total + t; + Add(timings, t); + if opt.showProgress then + #Print(".\c"); + Print(['\r', "|/-\\"[1+(i mod 4)],'\c']); + fi; + od; + if opt.showProgress then + #Print("\n"); + Print("\r \c\r"); + fi; + + Sort(timings); + + res := rec(); + #res.timings := timings; + res.total := total; + res.counter := i; + res.avg := Sum(timings) * 1.0 / res.counter; + res.var := Sum(timings, t -> (t-res.avg)^2) / res.counter; + res.std := Sqrt(res.var); + if IsOddInt(res.counter) then + res.median := timings[(res.counter+1)/2]; + else + res.median := (timings[(res.counter)/2] + timings[(res.counter+2)/2]) / 2.; + fi; + # TODO: discard outliers? + + if opt.showSummary then + Print("Performed ", res.counter, " iterations, taking ", Round(res.total), " milliseconds.\n"); + #Print("timings: ", timings, "\n"); + Print("average: ", Round(100*res.avg)/100, + " +/- ", Round(100*res.std)/100, + " (+/- ", Round(100*res.std/res.avg), "%)", + "\n"); + Print("median: ", res.median, "\n"); + fi; + + return res; +end; + +if false then +x:=X(Integers); +Benchmark(function() Factors(x^293-1); end); +Benchmark(function() Factors(x^293-1); end, rec(maxreps:=10)); +fi;