mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
Transition microbenchmark (EMBED_BENCH) and a main-Worker-only profile
- T.CallBench, run by the embed program when EMBED_BENCH=N: ns per call to
a tiny NoInlining callee in four arrangements:
- same: caller and callee in the same tier;
- to-refused: JIT code calling a method the wasm JIT refuses (it holds
a calli that never runs);
- from-refused: the reverse;
- refused-refused.
With GEXMONO_WASM_JIT=0, every leg is the interpreter's own call.
- browser-harness: GEX_NT_PROFILE_US sets the sampling interval, and
GEX_NT_PROFILE_FIRST=N profiles only the first N matching Workers, with
none attaching later. Sampling all thirty of Hollow Knight's Workers
stalled its frames (hg10: 0 presents in the window).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
1 parent
42f09b39a4
commit
ce395febaf
3 files changed
+52
-2
No files matched your search
@@ -323,13 +323,23 @@ export async function openPage(query, { width = 1280, height = 900, shotDir = nu
|
||||
// Workers that start during the window (a game's threads) are profiled
|
||||
// from when they attach.
|
||||
const sids = [];
|
||||
// GEX_NT_PROFILE_US: the sampling interval (default 250 us). GEX_NT_PROFILE_FIRST=N: only the
|
||||
// first N matching Workers (a game's main Worker is the first), none that attach later --
|
||||
// sampling thirty Workers at once stalls a game's frames.
|
||||
const interval = Number(process.env.GEX_NT_PROFILE_US || 250);
|
||||
const first = Number(process.env.GEX_NT_PROFILE_FIRST || 0);
|
||||
const start = async () => {
|
||||
for (const s of [...(withPage ? [page] : []), ...[...targets].filter(([, u]) => u.includes(match)).map(([x]) => x)]) {
|
||||
let picked = [...targets].filter(([, u]) => u.includes(match)).map(([x]) => x);
|
||||
if (first) {
|
||||
if (sids.length) return;
|
||||
picked = picked.slice(0, first);
|
||||
}
|
||||
for (const s of [...(withPage ? [page] : []), ...picked]) {
|
||||
if (sids.includes(s)) continue;
|
||||
sids.push(s);
|
||||
try {
|
||||
await browser.send('Profiler.enable', {}, s);
|
||||
await browser.send('Profiler.setSamplingInterval', { interval: 250 }, s);
|
||||
await browser.send('Profiler.setSamplingInterval', { interval }, s);
|
||||
await browser.send('Profiler.start', {}, s);
|
||||
} catch { /* gone already */ }
|
||||
}
|
||||
|
||||
@@ -230,6 +230,14 @@ main (int argc, char **argv)
|
||||
P clr = mono_runtime_invoke (clears, NULL, clargs, &exc);
|
||||
check ("Clears", exc);
|
||||
printf ("%s\n", mono_string_to_utf8 (clr));
|
||||
if (getenv ("EMBED_BENCH")) { /* transition costs, for the bench box only */
|
||||
P cb = mono_class_get_method_from_name (k, "CallBench", 1);
|
||||
int cn = atoi (getenv ("EMBED_BENCH"));
|
||||
void *cargs [1] = { &cn };
|
||||
P cr = mono_runtime_invoke (cb, NULL, cargs, &exc);
|
||||
check ("CallBench", exc);
|
||||
fprintf (stderr, "%s\n", mono_string_to_utf8 (cr));
|
||||
}
|
||||
P dels = mono_class_get_method_from_name (k, "Delegates", 1);
|
||||
int dn = 40;
|
||||
void *dargs [1] = { &dn };
|
||||
|
||||
@@ -59,6 +59,38 @@ public static class T {
|
||||
}
|
||||
return "delegates " + sum + " " + tags;
|
||||
}
|
||||
// Call-transition costs (EMBED_BENCH): ns per call for a tiny callee, from a caller in the same
|
||||
// tier, from JIT code into a method the wasm JIT refuses (it holds a calli, never taken), and from
|
||||
// such a method into a JIT-compiled one. With GEXMONO_WASM_JIT=0 every leg is the interpreter's.
|
||||
[MethodImpl(MethodImplOptions.NoInlining)] static int BInc(int x) { return x + 1; }
|
||||
static unsafe delegate*<int, int> bnever = null;
|
||||
[MethodImpl(MethodImplOptions.NoInlining)] static unsafe int BIncInterp(int x) { if (x == int.MinValue) return bnever(x); return x + 1; }
|
||||
static int BLoopSame(int n) { int a = 0; for (int i = 0; i < n; i++) a = BInc(a); return a; }
|
||||
static int BLoopToInterp(int n) { int a = 0; for (int i = 0; i < n; i++) a = BIncInterp(a); return a; }
|
||||
static unsafe int BLoopFromInterp(int n) {
|
||||
int a = 0;
|
||||
if (n == int.MinValue) return bnever(n);
|
||||
for (int i = 0; i < n; i++) a = BInc(a);
|
||||
return a;
|
||||
}
|
||||
static unsafe int BLoopInterpInterp(int n) {
|
||||
int a = 0;
|
||||
if (n == int.MinValue) return bnever(n);
|
||||
for (int i = 0; i < n; i++) a = BIncInterp(a);
|
||||
return a;
|
||||
}
|
||||
public static string CallBench(int n) {
|
||||
var sb = new System.Text.StringBuilder("callbench ns/call:");
|
||||
foreach (var (name, f) in new (string, Func<int, int>)[] { ("same", BLoopSame), ("to-refused", BLoopToInterp), ("from-refused", BLoopFromInterp), ("refused-refused", BLoopInterpInterp) }) {
|
||||
f(1000); f(1000); // past the JIT threshold, compiled
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
int r = f(n);
|
||||
sw.Stop();
|
||||
sb.Append($" {name} {sw.Elapsed.TotalMilliseconds * 1e6 / n:F1}");
|
||||
if (r != n) sb.Append("(wrong)");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
public static object LvGraph(int n) {
|
||||
var r = new LvRoot { arr = new object[n], pairs = new Pair[n], list = new List<Leaf>(), s = "not a leaf" };
|
||||
for (int i = 0; i < n; i++) r.arr[i] = i % 2 == 0 ? new SubLeaf { id = i } : new Leaf { id = i };
|
||||
|
||||
Reference in new issue
Block a user