Add PVS benchmark (#23166)
* Add PVS benchmark * poke tests * Shuffle players around * Add caveat * Add CycleTick() benchmark * Make async false * Oops
This commit is contained in:
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> MapsSource { get; set; }
|
||||
public static readonly string[] MapsSource = { "Empty", "Box", "Aspid", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Gemini", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -23,13 +23,6 @@ namespace Content.Benchmarks
|
||||
|
||||
public static async Task MainAsync(string[] args)
|
||||
{
|
||||
PoolManager.Startup(typeof(Program).Assembly);
|
||||
var pair = await PoolManager.GetServerClient();
|
||||
var gameMaps = pair.Server.ResolveDependency<IPrototypeManager>().EnumeratePrototypes<GameMapPrototype>().ToList();
|
||||
MapLoadBenchmark.MapsSource = gameMaps.Select(x => x.ID);
|
||||
await pair.CleanReturnAsync();
|
||||
PoolManager.Shutdown();
|
||||
|
||||
#if DEBUG
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("\nWARNING: YOU ARE RUNNING A DEBUG BUILD, USE A RELEASE BUILD FOR AN ACCURATE BENCHMARK");
|
||||
|
||||
187
Content.Benchmarks/PvsBenchmark.cs
Normal file
187
Content.Benchmarks/PvsBenchmark.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
// This benchmark probably benefits from some accidental cache locality. I,e. the order in which entities in a pvs
|
||||
// chunk are sent to players matches the order in which the entities were spawned.
|
||||
//
|
||||
// in a real mid-late game round, this is probably no longer the case.
|
||||
// One way to somewhat offset this is to update the NetEntity assignment to assign random (but still unique) NetEntity uids to entities.
|
||||
// This makes the benchmark run noticeably slower.
|
||||
|
||||
[Virtual]
|
||||
public class PvsBenchmark
|
||||
{
|
||||
public const string Map = "Maps/box.yml";
|
||||
|
||||
[Params(1, 8, 80)]
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private ICommonSession[] _players = default!;
|
||||
private EntityCoordinates[] _spawns = default!;
|
||||
public int _cycleOffset = 0;
|
||||
private SharedTransformSystem _sys = default!;
|
||||
private EntityCoordinates[] _locations = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
#if !DEBUG
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
#endif
|
||||
PoolManager.Startup(null);
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
|
||||
_pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
|
||||
_sys = _entMan.System<SharedTransformSystem>();
|
||||
|
||||
// Spawn the map
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).Wait();
|
||||
|
||||
// Get list of ghost warp positions
|
||||
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
|
||||
.OrderBy(x => x.Component.Location)
|
||||
.Select(x => _entMan.GetComponent<TransformComponent>(x.Uid).Coordinates)
|
||||
.ToArray();
|
||||
|
||||
Array.Resize(ref _players, PlayerCount);
|
||||
|
||||
// Spawn "Players".
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
for (var i = 0; i < PlayerCount; i++)
|
||||
{
|
||||
var pos = _spawns[i % _spawns.Length];
|
||||
var uid =_entMan.SpawnEntity("MobHuman", pos);
|
||||
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
|
||||
_players[i] = new DummySession{AttachedEntity = uid};
|
||||
}
|
||||
}).Wait();
|
||||
|
||||
// Repeatedly move players around so that they "explore" the map and see lots of entities.
|
||||
// This will populate their PVS data with out-of-view entities.
|
||||
var rng = new Random(42);
|
||||
ShufflePlayers(rng, 100);
|
||||
|
||||
_pair.Server.PvsTick(_players);
|
||||
_pair.Server.PvsTick(_players);
|
||||
|
||||
var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
|
||||
_locations = ents.Select(x => _entMan.GetComponent<TransformComponent>(x).Coordinates).ToArray();
|
||||
}
|
||||
|
||||
private void ShufflePlayers(Random rng, int count)
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
ShufflePlayers(rng);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShufflePlayers(Random rng)
|
||||
{
|
||||
_pair.Server.PvsTick(_players);
|
||||
|
||||
var ents = _players.Select(x => x.AttachedEntity!.Value).ToArray();
|
||||
var locations = ents.Select(x => _entMan.GetComponent<TransformComponent>(x).Coordinates).ToArray();
|
||||
|
||||
// Shuffle locations
|
||||
var n = locations.Length;
|
||||
while (n > 1)
|
||||
{
|
||||
n -= 1;
|
||||
var k = rng.Next(n + 1);
|
||||
(locations[k], locations[n]) = (locations[n], locations[k]);
|
||||
}
|
||||
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
for (var i = 0; i < PlayerCount; i++)
|
||||
{
|
||||
_sys.SetCoordinates(ents[i], locations[i]);
|
||||
}
|
||||
}).Wait();
|
||||
|
||||
_pair.Server.PvsTick(_players);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic benchmark for PVS in a static situation where nothing moves or gets dirtied..
|
||||
/// This effectively provides a lower bound on "real" pvs tick time, as it is missing:
|
||||
/// - PVS chunks getting dirtied and needing to be rebuilt
|
||||
/// - Fetching component states for dirty components
|
||||
/// - Compressing & sending network messages
|
||||
/// - Sending PVS leave messages
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void StaticTick()
|
||||
{
|
||||
_pair.Server.PvsTick(_players);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic benchmark for PVS in a situation where players are teleporting all over the place. This isn't very
|
||||
/// realistic, but unlike <see cref="StaticTick"/> this will actually also measure the speed of processing dirty
|
||||
/// chunks and sending PVS leave messages.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public void CycleTick()
|
||||
{
|
||||
_cycleOffset = (_cycleOffset + 1) % _players.Length;
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
for (var i = 0; i < PlayerCount; i++)
|
||||
{
|
||||
_sys.SetCoordinates(_players[i].AttachedEntity!.Value, _locations[(i + _cycleOffset) % _players.Length]);
|
||||
}
|
||||
}).Wait();
|
||||
_pair.Server.PvsTick(_players);
|
||||
}
|
||||
|
||||
private sealed class DummySession : ICommonSession
|
||||
{
|
||||
public SessionStatus Status => SessionStatus.InGame;
|
||||
public EntityUid? AttachedEntity {get; set; }
|
||||
public NetUserId UserId => default;
|
||||
public string Name => string.Empty;
|
||||
public short Ping => default;
|
||||
public INetChannel Channel { get; set; } = default!;
|
||||
public LoginType AuthType => default;
|
||||
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
|
||||
public DateTime ConnectedTime { get; set; }
|
||||
public SessionState State => default!;
|
||||
public SessionData Data => default!;
|
||||
public bool ClientSide { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user