|
| 1 | +using System.Reflection; |
| 2 | + |
| 3 | +public sealed record ComparisonCase(string Name, string Category, string Source, double Expected) |
| 4 | +{ |
| 5 | + public override string ToString() => Name; |
| 6 | +} |
| 7 | + |
| 8 | +public static class ComparisonWorkloads |
| 9 | +{ |
| 10 | + // ASCII input keeps the UTF-16, byte-string and native UTF-8 engines comparable. |
| 11 | + // Every invocation rebuilds its mutable Lua data and returns one numeric checksum. |
| 12 | + public static readonly ComparisonCase[] Cases = |
| 13 | + [ |
| 14 | + new("numeric-loop", "VM", "local s=0; for i=1,20000 do s=s+i end; return s", 200010000), |
| 15 | + new( |
| 16 | + "calls-varargs", |
| 17 | + "VM", |
| 18 | + "local function f(a,...) local b,c=...; return a+b+c end; local s=0; for i=1,2000 do s=s+f(i,2,3) end; return s", |
| 19 | + 2011000 |
| 20 | + ), |
| 21 | + new( |
| 22 | + "table-access", |
| 23 | + "Tables", |
| 24 | + "local t={}; for i=1,1000 do t[i]={x=i,y=i%7} end; local s=0; for i=1,1000 do local v=t[i]; s=s+v.x+v.y end; return s", |
| 25 | + 503503 |
| 26 | + ), |
| 27 | + new( |
| 28 | + "metamethods", |
| 29 | + "VM", |
| 30 | + "local mt={__add=function(a,b) return a.x+b.x end,__index=function(t,k) return 5 end}; local a,b=setmetatable({x=3},mt),setmetatable({x=4},mt); local s=0; for i=1,2000 do s=s+(a+b)+a.missing end; return s", |
| 31 | + 24000 |
| 32 | + ), |
| 33 | + new( |
| 34 | + "math", |
| 35 | + "Standard", |
| 36 | + "local abs,sqrt,min,max=math.abs,math.sqrt,math.min,math.max; local s=0; for i=1,2000 do s=s+min(abs(-i),sqrt(i))+max(i,3,7) end; return s", |
| 37 | + MathExpected() |
| 38 | + ), |
| 39 | + new( |
| 40 | + "strings", |
| 41 | + "Standard", |
| 42 | + "local rep,reverse,sub,byte,concat=string.rep,string.reverse,string.sub,string.byte,table.concat; local n=0; local t={'abc',12,'def',34}; for i=1,400 do local s=sub(reverse(rep('abcd',8,':')),2,-2); n=n+byte(s)+#s+#concat(t,',') end; return n", |
| 43 | + 59600 |
| 44 | + ), |
| 45 | + new( |
| 46 | + "patterns", |
| 47 | + "Standard", |
| 48 | + "local gsub=string.gsub; local n=0; for i=1,400 do local s=gsub('hello 123 world 456','(%a+)', '[%1]'); n=n+#s end; return n", |
| 49 | + 9200 |
| 50 | + ), |
| 51 | + new( |
| 52 | + "table-sort", |
| 53 | + "Tables", |
| 54 | + "local t={}; for i=1,300 do t[i]=301-i end; table.sort(t); return t[150]", |
| 55 | + 150 |
| 56 | + ), |
| 57 | + new( |
| 58 | + "coroutines", |
| 59 | + "Coroutines", |
| 60 | + "local co=coroutine.create(function() for i=1,500 do coroutine.yield(i) end end); local s=0; for i=1,500 do local ok,x=coroutine.resume(co); assert(ok); s=s+x end; return s", |
| 61 | + 125250 |
| 62 | + ), |
| 63 | + new( |
| 64 | + "host-calls", |
| 65 | + "Interop", |
| 66 | + "local add=host_add; local s=0; for i=1,2000 do s=add(s,i) end; return s", |
| 67 | + 2001000 |
| 68 | + ), |
| 69 | + new("nbody", "Mixed", LoadNBody(), -0.1690507623824094), |
| 70 | + ]; |
| 71 | + |
| 72 | + public static readonly ComparisonCase[] CompilationCases = |
| 73 | + [ |
| 74 | + Find("numeric-loop"), |
| 75 | + Find("nbody"), |
| 76 | + new( |
| 77 | + "control-flow", |
| 78 | + "Compiler", |
| 79 | + "local total=0;" |
| 80 | + + string.Concat( |
| 81 | + Enumerable.Repeat( |
| 82 | + "do local function choose(value) if not value or value==false then return nil elseif value==true then return 1 else return value end end; " |
| 83 | + + "local value=choose(2); repeat value=value-1 until value==0; while value<2 do value=value+1; if value==1 then goto skip end; total=total+value; ::skip:: end; " |
| 84 | + + "for index=1,2 do if index==2 then break end; total=total+index end end;", |
| 85 | + 40 |
| 86 | + ) |
| 87 | + ) |
| 88 | + + "return total", |
| 89 | + 120 |
| 90 | + ), |
| 91 | + new( |
| 92 | + "identifiers", |
| 93 | + "Compiler", |
| 94 | + "local total=0;" |
| 95 | + + string.Concat( |
| 96 | + Enumerable |
| 97 | + .Range(0, 80) |
| 98 | + .Select(i => |
| 99 | + $"do local returnValue{i},functionName{i},ordinaryIdentifier{i}=1,2,3; total=total+returnValue{i}+functionName{i}+ordinaryIdentifier{i} end;" |
| 100 | + ) |
| 101 | + ) |
| 102 | + + "return total", |
| 103 | + 480 |
| 104 | + ), |
| 105 | + StringConstants(false), |
| 106 | + StringConstants(true), |
| 107 | + ]; |
| 108 | + |
| 109 | + static ComparisonCase StringConstants(bool escaped) |
| 110 | + { |
| 111 | + var source = new System.Text.StringBuilder("local total=0;"); |
| 112 | + var expected = 0; |
| 113 | + for (var i = 0; i < 80; i++) |
| 114 | + { |
| 115 | + var decoded = escaped |
| 116 | + ? $"escaped value {i}:\tcolumn\nline\r\a\b\f\v\\\"'AAsuffix" |
| 117 | + : $"ordinary string value {i}: the quick brown fox jumps over the lazy dog"; |
| 118 | + var literal = escaped |
| 119 | + ? $"escaped value {i}:\\tcolumn\\nline\\r\\a\\b\\f\\v\\\\\\\"\\'\\x41\\065\\z suffix" |
| 120 | + : decoded; |
| 121 | + source.Append("do local value='").Append(literal).Append("'; total=total+#value end;"); |
| 122 | + expected += decoded.Length; |
| 123 | + } |
| 124 | + source.Append("return total"); |
| 125 | + return new( |
| 126 | + escaped ? "escaped-strings" : "plain-strings", |
| 127 | + "Compiler", |
| 128 | + source.ToString(), |
| 129 | + expected |
| 130 | + ); |
| 131 | + } |
| 132 | + |
| 133 | + public static ComparisonCase FindCompilation(string name) => |
| 134 | + CompilationCases.Single(c => c.Name == name); |
| 135 | + |
| 136 | + static double MathExpected() |
| 137 | + { |
| 138 | + double sum = 0; |
| 139 | + for (var i = 1; i <= 2000; i++) |
| 140 | + sum += Math.Sqrt(i) + Math.Max(i, 7); |
| 141 | + return sum; |
| 142 | + } |
| 143 | + |
| 144 | + static string LoadNBody() |
| 145 | + { |
| 146 | + using var stream = Assembly |
| 147 | + .GetExecutingAssembly() |
| 148 | + .GetManifestResourceStream("Comparison.n-body.lua")!; |
| 149 | + using var reader = new StreamReader(stream); |
| 150 | + return reader.ReadToEnd().Replace("or 1000", "or 100") + "\nreturn energy(bodies,nbody)"; |
| 151 | + } |
| 152 | + |
| 153 | + public static ComparisonCase Find(string name) => Cases.Single(c => c.Name == name); |
| 154 | + |
| 155 | + public static void Check(ComparisonCase workload, string engine, double actual) |
| 156 | + { |
| 157 | + var tolerance = Math.Max(1, Math.Abs(workload.Expected)) * 1e-10; |
| 158 | + if (!double.IsFinite(actual) || Math.Abs(actual - workload.Expected) > tolerance) |
| 159 | + throw new InvalidOperationException( |
| 160 | + $"{engine}/{workload.Name}: expected {workload.Expected:R}, got {actual:R}" |
| 161 | + ); |
| 162 | + } |
| 163 | +} |
0 commit comments