Skip to content

Commit 75511b3

Browse files
committed
add benchmark support assets
1 parent bad7b7b commit 75511b3

16 files changed

Lines changed: 2212 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.IO;
3+
using UnityEngine;
4+
5+
public class BenchmarkLogger
6+
{
7+
private string implementation;
8+
private StreamWriter csvWriter;
9+
10+
public BenchmarkLogger(string implementation, string outputPath)
11+
{
12+
this.implementation = implementation;
13+
csvWriter = new StreamWriter(outputPath, false);
14+
csvWriter.WriteLine("timestamp,implementation,benchmark,metric,value,payload_size");
15+
}
16+
17+
public void LogResult(string benchmark, int payloadSize, string metric, double value)
18+
{
19+
string line = string.Format("{0},{1},{2},{3},{4:F6},{5}",
20+
DateTime.UtcNow.ToString("o"), implementation, benchmark, metric, value, payloadSize);
21+
csvWriter.WriteLine(line);
22+
csvWriter.Flush();
23+
Debug.Log(string.Format("[{0}] {1} (payload={2}B): {3} = {4:F3}",
24+
implementation, benchmark, payloadSize, metric, value));
25+
}
26+
27+
public void LogSummary(string benchmark, int payloadSize, BenchmarkStats stats)
28+
{
29+
Debug.Log(string.Format("[{0}] {1} (payload={2}B): {3}",
30+
implementation, benchmark, payloadSize, stats));
31+
}
32+
33+
public void Close()
34+
{
35+
if (csvWriter != null)
36+
{
37+
csvWriter.Close();
38+
csvWriter = null;
39+
}
40+
}
41+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
public class BenchmarkStats
5+
{
6+
public double Min { get; private set; }
7+
public double Max { get; private set; }
8+
public double Average { get; private set; }
9+
public double Median { get; private set; }
10+
public double P95 { get; private set; }
11+
public double P99 { get; private set; }
12+
public double StdDev { get; private set; }
13+
public int Count { get; private set; }
14+
15+
public BenchmarkStats(List<double> samples)
16+
{
17+
Count = samples.Count;
18+
if (Count == 0) return;
19+
20+
var sorted = new List<double>(samples);
21+
sorted.Sort();
22+
23+
Min = sorted[0];
24+
Max = sorted[Count - 1];
25+
26+
double sum = 0;
27+
for (int i = 0; i < Count; i++) sum += sorted[i];
28+
Average = sum / Count;
29+
30+
Median = Percentile(sorted, 50);
31+
P95 = Percentile(sorted, 95);
32+
P99 = Percentile(sorted, 99);
33+
34+
double sumSquares = 0;
35+
for (int i = 0; i < Count; i++)
36+
{
37+
double diff = sorted[i] - Average;
38+
sumSquares += diff * diff;
39+
}
40+
StdDev = Math.Sqrt(sumSquares / Count);
41+
}
42+
43+
private static double Percentile(List<double> sorted, double percentile)
44+
{
45+
double index = (percentile / 100.0) * (sorted.Count - 1);
46+
int lower = (int)Math.Floor(index);
47+
int upper = (int)Math.Ceiling(index);
48+
if (lower == upper) return sorted[lower];
49+
double frac = index - lower;
50+
return sorted[lower] * (1 - frac) + sorted[upper] * frac;
51+
}
52+
53+
public override string ToString()
54+
{
55+
return string.Format("n={0} min={1:F3} avg={2:F3} med={3:F3} p95={4:F3} p99={5:F3} max={6:F3} stddev={7:F3}",
56+
Count, Min, Average, Median, P95, P99, Max, StdDev);
57+
}
58+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Text;
6+
using UnityEngine;
7+
using NativeWebSocket;
8+
9+
public class BurstBench
10+
{
11+
public IEnumerator Run(string serverUrl, int burstCount, BenchmarkLogger logger)
12+
{
13+
int payloadSize = 256;
14+
var ws = new WebSocket(serverUrl);
15+
bool connected = false;
16+
bool closed = false;
17+
int received = 0;
18+
bool burstDone = false;
19+
var frameReceiveCounts = new List<int>();
20+
var frameTimesMs = new List<double>();
21+
22+
ws.OnOpen += () => connected = true;
23+
ws.OnMessage += (data) =>
24+
{
25+
// Check for burst_done signal
26+
if (data.Length < 20)
27+
{
28+
string msg = Encoding.UTF8.GetString(data);
29+
if (msg == "burst_done")
30+
{
31+
burstDone = true;
32+
return;
33+
}
34+
}
35+
received++;
36+
};
37+
ws.OnClose += (code) => closed = true;
38+
39+
_ = ws.Connect();
40+
41+
float timeout = 0;
42+
while (!connected && !closed && timeout < 10f)
43+
{
44+
ws.DispatchMessageQueue();
45+
timeout += Time.unscaledDeltaTime;
46+
yield return null;
47+
}
48+
if (!connected) yield break;
49+
50+
// Warmup
51+
yield return new WaitForSeconds(0.5f);
52+
ws.DispatchMessageQueue();
53+
54+
// Request burst
55+
received = 0;
56+
burstDone = false;
57+
var sw = Stopwatch.StartNew();
58+
_ = ws.SendText(string.Format("burst:{0}:{1}", burstCount, payloadSize));
59+
60+
var frameSw = new Stopwatch();
61+
62+
while (!burstDone && sw.Elapsed.TotalSeconds < 10)
63+
{
64+
int beforeCount = received;
65+
66+
frameSw.Restart();
67+
ws.DispatchMessageQueue();
68+
frameSw.Stop();
69+
70+
int thisFrame = received - beforeCount;
71+
if (thisFrame > 0)
72+
{
73+
frameReceiveCounts.Add(thisFrame);
74+
frameTimesMs.Add(frameSw.Elapsed.TotalMilliseconds);
75+
}
76+
77+
yield return null;
78+
}
79+
80+
sw.Stop();
81+
82+
double totalTimeMs = sw.Elapsed.TotalMilliseconds;
83+
int peakPerFrame = 0;
84+
double peakFrameTime = 0;
85+
for (int i = 0; i < frameReceiveCounts.Count; i++)
86+
{
87+
if (frameReceiveCounts[i] > peakPerFrame) peakPerFrame = frameReceiveCounts[i];
88+
}
89+
for (int i = 0; i < frameTimesMs.Count; i++)
90+
{
91+
if (frameTimesMs[i] > peakFrameTime) peakFrameTime = frameTimesMs[i];
92+
}
93+
94+
logger.LogResult("Burst", payloadSize, "total_time_ms", totalTimeMs);
95+
logger.LogResult("Burst", payloadSize, "messages_received", received);
96+
logger.LogResult("Burst", payloadSize, "peak_per_frame", peakPerFrame);
97+
logger.LogResult("Burst", payloadSize, "peak_frame_time_ms", peakFrameTime);
98+
logger.LogResult("Burst", payloadSize, "frames_to_process", frameReceiveCounts.Count);
99+
100+
if (frameTimesMs.Count > 0)
101+
{
102+
var ftStats = new BenchmarkStats(frameTimesMs);
103+
logger.LogResult("Burst", payloadSize, "avg_frame_time_ms", ftStats.Average);
104+
logger.LogResult("Burst", payloadSize, "p99_frame_time_ms", ftStats.P99);
105+
}
106+
107+
UnityEngine.Debug.Log(string.Format(
108+
"Burst: {0}/{1} msgs in {2:F1}ms, peak {3}/frame, peak frame {4:F3}ms",
109+
received, burstCount, totalTimeMs, peakPerFrame, peakFrameTime));
110+
111+
closed = false;
112+
_ = ws.Close();
113+
timeout = 0;
114+
while (!closed && timeout < 5f)
115+
{
116+
ws.DispatchMessageQueue();
117+
timeout += Time.unscaledDeltaTime;
118+
yield return null;
119+
}
120+
}
121+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections;
3+
using UnityEngine;
4+
using NativeWebSocket;
5+
6+
public class GCAllocBench
7+
{
8+
public IEnumerator Run(string serverUrl, int payloadSize, BenchmarkLogger logger)
9+
{
10+
var ws = new WebSocket(serverUrl);
11+
bool connected = false;
12+
bool closed = false;
13+
int receivedCount = 0;
14+
15+
ws.OnOpen += () => connected = true;
16+
ws.OnMessage += (data) => receivedCount++;
17+
ws.OnClose += (code) => closed = true;
18+
19+
_ = ws.Connect();
20+
21+
float timeout = 0;
22+
while (!connected && !closed && timeout < 10f)
23+
{
24+
ws.DispatchMessageQueue();
25+
timeout += Time.unscaledDeltaTime;
26+
yield return null;
27+
}
28+
if (!connected) yield break;
29+
30+
byte[] payload = new byte[payloadSize];
31+
int iterations = 500;
32+
33+
// --- Warmup ---
34+
for (int i = 0; i < 50; i++) { _ = ws.Send(payload); }
35+
yield return null;
36+
ws.DispatchMessageQueue();
37+
yield return new WaitForSeconds(0.5f);
38+
ws.DispatchMessageQueue();
39+
40+
// --- Measure Send allocations ---
41+
GC.Collect();
42+
GC.WaitForPendingFinalizers();
43+
GC.Collect();
44+
45+
int gen0Before = GC.CollectionCount(0);
46+
int gen1Before = GC.CollectionCount(1);
47+
int gen2Before = GC.CollectionCount(2);
48+
long memBefore = GC.GetTotalMemory(false);
49+
50+
for (int i = 0; i < iterations; i++) { _ = ws.Send(payload); }
51+
52+
long memAfter = GC.GetTotalMemory(false);
53+
int gen0After = GC.CollectionCount(0);
54+
int gen1After = GC.CollectionCount(1);
55+
int gen2After = GC.CollectionCount(2);
56+
57+
double sendAllocPerOp = (double)(memAfter - memBefore) / iterations;
58+
logger.LogResult("GCAlloc_Send", payloadSize, "bytes_per_op", sendAllocPerOp);
59+
logger.LogResult("GCAlloc_Send", payloadSize, "gen0_collections", gen0After - gen0Before);
60+
logger.LogResult("GCAlloc_Send", payloadSize, "gen1_collections", gen1After - gen1Before);
61+
logger.LogResult("GCAlloc_Send", payloadSize, "gen2_collections", gen2After - gen2Before);
62+
63+
// Wait for echoes to arrive
64+
yield return new WaitForSeconds(2f);
65+
66+
// --- Measure Receive/Dispatch allocations ---
67+
// Ask server to flood, then wait for messages to queue up
68+
receivedCount = 0;
69+
_ = ws.SendText(string.Format("flood:{0}:{1}", iterations, payloadSize));
70+
yield return new WaitForSeconds(2f);
71+
72+
GC.Collect();
73+
GC.WaitForPendingFinalizers();
74+
GC.Collect();
75+
76+
gen0Before = GC.CollectionCount(0);
77+
memBefore = GC.GetTotalMemory(false);
78+
int countBefore = receivedCount;
79+
80+
// Dispatch queued messages
81+
ws.DispatchMessageQueue();
82+
83+
memAfter = GC.GetTotalMemory(false);
84+
gen0After = GC.CollectionCount(0);
85+
int dispatched = receivedCount - countBefore;
86+
87+
if (dispatched > 0)
88+
{
89+
double dispatchAllocPerOp = (double)(memAfter - memBefore) / dispatched;
90+
logger.LogResult("GCAlloc_Dispatch", payloadSize, "bytes_per_op", dispatchAllocPerOp);
91+
logger.LogResult("GCAlloc_Dispatch", payloadSize, "messages_dispatched", dispatched);
92+
logger.LogResult("GCAlloc_Dispatch", payloadSize, "gen0_collections", gen0After - gen0Before);
93+
}
94+
95+
UnityEngine.Debug.Log(string.Format("GC: send={0:F0} bytes/op, dispatch={1} bytes/op (n={2})",
96+
sendAllocPerOp,
97+
dispatched > 0 ? ((double)(memAfter - memBefore) / dispatched).ToString("F0") : "N/A",
98+
dispatched));
99+
100+
closed = false;
101+
_ = ws.Close();
102+
timeout = 0;
103+
while (!closed && timeout < 5f)
104+
{
105+
ws.DispatchMessageQueue();
106+
timeout += Time.unscaledDeltaTime;
107+
yield return null;
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)