Skip to content
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
2832218
Initial system groundwork
ArtisticRoomba Jul 24, 2025
a7203de
Basic AtmosphereSystem and DeltaPressureSystem API
ArtisticRoomba Jul 24, 2025
e72f6f2
Initial MVP
ArtisticRoomba Jul 25, 2025
4f7afe9
First round of optimizations
ArtisticRoomba Jul 26, 2025
62ae561
Second round of optimizations
ArtisticRoomba Jul 26, 2025
83fc462
Third round of optimizations
ArtisticRoomba Jul 27, 2025
05720dd
Microfix
ArtisticRoomba Jul 27, 2025
4c27c53
Misc cleanup
ArtisticRoomba Jul 27, 2025
f51cf22
lol
ArtisticRoomba Jul 28, 2025
743325f
Cache recently checked tiles
ArtisticRoomba Jul 28, 2025
71f4e3e
Fix tile caching invalidation
ArtisticRoomba Jul 28, 2025
1859583
Implement basic multithreading
ArtisticRoomba Jul 29, 2025
810f36a
Revert "Implement basic multithreading"
ArtisticRoomba Jul 29, 2025
a995848
Cleanup, CVAR toggle
ArtisticRoomba Jul 29, 2025
0587ef8
More cleanup and damage logic refac
ArtisticRoomba Jul 29, 2025
fc643eb
Move methods in AtmosphereSystem.DeltaPressure.cs over to DeltaPressu…
ArtisticRoomba Jul 30, 2025
e7f652f
Expand DeltaPressureSystem.cs API
ArtisticRoomba Jul 31, 2025
e89c90e
Add locale
ArtisticRoomba Jul 31, 2025
6015999
Adjust default components
ArtisticRoomba Aug 1, 2025
bdeb5e2
Add damage values to windows
ArtisticRoomba Aug 1, 2025
4b4a167
Add damage values to windoors
ArtisticRoomba Aug 1, 2025
5d70985
Add damage values to uranium windows
ArtisticRoomba Aug 1, 2025
4ed6c94
Add damage values to shuttle windows
ArtisticRoomba Aug 1, 2025
1119fc3
Actually enable small damage numbers to be applied
ArtisticRoomba Aug 1, 2025
cb1e886
Make most API methods static
ArtisticRoomba Aug 1, 2025
71c4fd5
Add WIP option for window damage randomness
ArtisticRoomba Aug 2, 2025
826d7d4
Do randomness properly this time
ArtisticRoomba Aug 2, 2025
5d8a33e
Address review
ArtisticRoomba Aug 11, 2025
a56ff49
base
ArtisticRoomba Aug 12, 2025
a76ff69
initial commit
ArtisticRoomba Aug 13, 2025
574bfa5
misc
ArtisticRoomba Aug 13, 2025
89b17ce
misc
ArtisticRoomba Aug 14, 2025
56823f3
fix params
ArtisticRoomba Aug 14, 2025
cbd9069
Revert "base"
ArtisticRoomba Aug 14, 2025
40570cf
Parallel solve initial commit
ArtisticRoomba Aug 14, 2025
7c150fe
Merge branch 'atmos/delta-pressure-benchmark' into atmos/delta-pressu…
ArtisticRoomba Aug 14, 2025
08d5b85
misc optimizations
ArtisticRoomba Aug 14, 2025
bda1722
tune default vars
ArtisticRoomba Aug 15, 2025
130096e
misc cleanup
ArtisticRoomba Aug 15, 2025
f51323a
get rid of DeltaPressureBenchmark.cs
ArtisticRoomba Aug 15, 2025
b44953b
Cleanup using directives
ArtisticRoomba Aug 15, 2025
1b374b7
Revert "get rid of DeltaPressureBenchmark.cs"
ArtisticRoomba Aug 15, 2025
b898ada
More parallel solve CVARs
ArtisticRoomba Aug 15, 2025
c709356
Fix mistake
ArtisticRoomba Aug 15, 2025
6d35510
Merge branch 'atmos/delta-pressure' into atmos/delta-pressure-benchmark
ArtisticRoomba Aug 15, 2025
80bbe23
Modify params
ArtisticRoomba Aug 16, 2025
5fa7af1
Modify API
ArtisticRoomba Aug 17, 2025
4026a31
Fix entity leak
ArtisticRoomba Aug 17, 2025
c04bc7a
Fix API for grid ref
ArtisticRoomba Aug 17, 2025
76524f0
Two tests
ArtisticRoomba Aug 17, 2025
3229a24
Merge branch 'atmos/delta-pressure' into atmos/delta-pressure-tests
ArtisticRoomba Aug 17, 2025
3cc527b
directional pressure tests
ArtisticRoomba Aug 19, 2025
bd4f067
shitcoded static pressure tests
ArtisticRoomba Aug 19, 2025
3a7548e
cleanup
ArtisticRoomba Aug 19, 2025
e03820c
SIMD accel bulk pressure retrieval plus cleanup
ArtisticRoomba Aug 20, 2025
32d23af
Assert on dict/list check for sync
ArtisticRoomba Aug 20, 2025
f5e04cb
Fix API and add TODO
ArtisticRoomba Aug 21, 2025
2cf5034
Docs
ArtisticRoomba Aug 22, 2025
339ad47
fix out of bounds issues
ArtisticRoomba Aug 22, 2025
cc9da16
improve comments
ArtisticRoomba Aug 23, 2025
25da8d3
fix NaN in bulk processing
ArtisticRoomba Aug 26, 2025
d79a793
csgrad
ArtisticRoomba Aug 26, 2025
59a79c0
Merge branch 'atmos/delta-pressure-tests' into atmos/delta-pressure
ArtisticRoomba Sep 1, 2025
b6ec066
Merge branch 'atmos/delta-pressure-benchmark' into atmos/delta-pressure
ArtisticRoomba Sep 1, 2025
386a2de
Major benchmarking changes to reduce measurement overhead, microoptim…
ArtisticRoomba Sep 2, 2025
8d43656
Cache current ent pos to prevent relatively expensive indices lookup
ArtisticRoomba Sep 2, 2025
2261975
Address review
ArtisticRoomba Sep 2, 2025
160ba07
Misc fixes
ArtisticRoomba Sep 2, 2025
8dc4c94
Comment fixe
ArtisticRoomba Sep 2, 2025
74b066c
cleanup using directives
ArtisticRoomba Sep 2, 2025
0e14ad1
Adjust default config values
ArtisticRoomba Sep 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions Content.Benchmarks/DeltaPressureBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;

namespace Content.Benchmarks;

/// <summary>
/// Spawns N number of entities with a <see cref="DeltaPressureComponent"/> and
/// simulates them for a number of ticks M.
/// </summary>
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
[ThreadingDiagnoser]
public class DeltaPressureBenchmark
{
/// <summary>
/// Number of entities (windows, really) to spawn with a <see cref="DeltaPressureComponent"/>.
/// </summary>
[Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
public int EntityCount;

/// <summary>
/// Number of entities that each parallel processing job will handle.
/// </summary>
// [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
[Params(10)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be a magic number, or at least this should match the default in the component, which is also a magic number currently set at 50. I have also said this multiple times, but 10 or 50 sound way too small. Can you profile this and determine what a "good" batch size is?

I have also said multiple times that this needs to be plotted against the number of parallel jobs on the x axis, not the number of entities. We already know it scales linearly with the number of entities.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also not a blocking issue -- it's good enough that we have a benchmark in the first place.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me go ahead and do some more profiling now that I have a better understanding of what we're looking for

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear -- we are trying to use profiling to determine the best batch size and iteration size for a representative but high-ish end of entities, e.g. the number of entities that you can reasonably expect on a large map x2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'm doing more profiling but I'll at least show what I've got so far: a really, really, shit chart. Sorry you have to look at it. But it does give us insights, and I'll get some more soon. All graphs are for PerformFullProcess.

image

Bear with me:

  • The series represents how many entities are being submitted for processing per run.
  • Ignore the datapoints beyond X = 5000, as that indicates Entities => BatchSize, AKA single thread performance.
  • You can see that if we submit not enough entities to process in a loop iteration our performance gets reduced to single-threaded equivalents.
  • If we submit the same amount of entities that we process in a batch, performance gets reduced to single-threaded equivalents.

This is a similar case for processing 100,000 entities:

100000entbatchsizevstime

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for doing this.

So, if I'm interpreting this correctly, it seems like a (small to me) batch size of 10 is actually the optimal batch size? If so, we should set the default to that.

I would still be curious in seeing how performance changes with respect to the number of parallel jobs. But that can be done at a later time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default set. Still scratching my head on the parallelism speedup in general, but I'll probably spend some time reading up on it later.

public int BatchSize;

/// <summary>
/// Number of entities to process per iteration in the DeltaPressure
/// processing loop.
/// </summary>
// [Params(100, 1000, 5000, 10000, 50000)]
[Params(100)]
public int EntitiesPerIteration;

private readonly EntProtoId _windowProtoId = "Window";
private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";

private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private SharedMapSystem _map = default!;
private IRobustRandom _random = default!;
private IConfigurationManager _cvar = default!;
private ITileDefinitionManager _tileDefMan = default!;
private AtmosphereSystem _atmospereSystem = default!;

private Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>
_testEnt;

[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;

var mapdata = await _pair.CreateTestMap();

_entMan = server.ResolveDependency<IEntityManager>();
_map = _entMan.System<SharedMapSystem>();
_random = server.ResolveDependency<IRobustRandom>();
_cvar = server.ResolveDependency<IConfigurationManager>();
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
_atmospereSystem = _entMan.System<AtmosphereSystem>();

_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.

_cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
_cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);

var plating = _tileDefMan["Plating"].TileId;

/*
Basically, we want to have a 5-wide grid of tiles.
Edges are walled, and the length of the grid is determined by N + 2.
Windows should only touch the top and bottom walls, and each other.
*/

var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
const int height = 5;

await server.WaitPost(() =>
{
// Fill required tiles (extend grid) with plating
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
}
}

// Spawn perimeter walls and windows row in the middle (y = 2)
const int midY = height / 2;
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);

var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
if (isPerimeter)
{
_entMan.SpawnEntity(_wallProtoId, coords);
continue;
}

// Spawn windows only on the middle row, spanning interior (excluding side walls)
if (y == midY)
{
_entMan.SpawnEntity(_windowProtoId, coords);
}
}
}
});

// Next we run the fixgridatmos command to ensure that we have some air on our grid.
// Wait a little bit as well.
// TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
// a stamp-on in AtmosphereSystem.
await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);

var uid = mapdata.Grid.Owner;
_testEnt = new Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>(
uid,
_entMan.GetComponent<GridAtmosphereComponent>(uid),
_entMan.GetComponent<GasTileOverlayComponent>(uid),
_entMan.GetComponent<MapGridComponent>(uid),
_entMan.GetComponent<TransformComponent>(uid));
}

[Benchmark]
public async Task PerformFullProcess()
{
await _pair.Server.WaitPost(() =>
{
while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
});
}

[Benchmark]
public async Task PerformSingleRunProcess()
{
await _pair.Server.WaitPost(() =>
{
_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
});
}

[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
Loading
Loading