perf: Budget less memory for Replay Checkpoints (#28052) (#5145)

* perf: Replays use less memory for checkpoints (#28052)

- Simple change of the CVars and some stats
- Based on a Lizard replay, checkpoints move from on average every 70 ticks to every 350.

* Set a minimum number of ticks that must pass between checkpoints

* Fix stat collection, split _checkpointMinInterval, more CheckpointState

* update release notes
This commit is contained in:
Tom Leys
2024-05-23 17:35:10 +12:00
committed by GitHub
parent 512ebd8422
commit b0922b8e0e
6 changed files with 40 additions and 10 deletions

View File

@@ -39,7 +39,7 @@ END TEMPLATE-->
### New features
*None yet*
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
### Bugfixes
@@ -47,7 +47,7 @@ END TEMPLATE-->
### Other
*None yet*
* The default values of various replay related cvars have been changed to try and reduce memory usage.
### Internal

View File

@@ -133,6 +133,11 @@ public sealed partial class ReplayLoadManager
var spawnedTracker = 0;
var stateTracker = 0;
var curState = state0;
var stats_due_ticks = 0;
var stats_due_spawned = 0;
var stats_due_state = 0;
for (var i = 1; i < states.Count; i++)
{
if (i % 10 == 0)
@@ -150,11 +155,28 @@ public sealed partial class ReplayLoadManager
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
// Don't create checkpoints too frequently no matter the circumstance
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
continue;
// Check if enough time, spawned entities or changed states have occurred.
if (ticksSinceLastCheckpoint < _checkpointInterval
&& spawnedTracker < _checkpointEntitySpawnThreshold
&& stateTracker < _checkpointEntityStateThreshold)
{
continue;
// Track and update statistics about why checkpoints are getting created:
if (ticksSinceLastCheckpoint >= _checkpointInterval)
{
stats_due_ticks += 1;
}
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
{
stats_due_spawned += 1;
}
else if (stateTracker >= _checkpointEntityStateThreshold)
{
stats_due_state += 1;
}
ticksSinceLastCheckpoint = 0;
@@ -169,7 +191,8 @@ public sealed partial class ReplayLoadManager
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
}
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
return (checkPoints.ToArray(), serverTime);
}

View File

@@ -34,6 +34,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
private ushort _metaId;
private bool _initialized;
private int _checkpointInterval;
private int _checkpointMinInterval;
private int _checkpointEntitySpawnThreshold;
private int _checkpointEntityStateThreshold;
private ISawmill _sawmill = default!;
@@ -45,6 +46,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
_initialized = true;
_confMan.OnValueChanged(CVars.CheckpointInterval, value => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointMinInterval, value => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointEntitySpawnThreshold, value => _checkpointEntitySpawnThreshold = value,
true);
_confMan.OnValueChanged(CVars.CheckpointEntityStateThreshold, value => _checkpointEntityStateThreshold = value,

View File

@@ -41,12 +41,12 @@ internal sealed partial class ReplayPlaybackManager
skipEffectEvents = true;
ResetToNearestCheckpoint(value, false);
}
else if (value > Replay.CurrentIndex + _checkpointInterval)
else if (value > Replay.CurrentIndex + _checkpointMinInterval)
{
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
// applying every tick.
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
if (nextCheckpoint.Index < value)
if (nextCheckpoint.Index < value && nextCheckpoint.Index > Replay.CurrentIndex)
ResetToNearestCheckpoint(value, false);
}

View File

@@ -51,7 +51,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
public ReplayData? Replay { get; private set; }
public NetUserId? Recorder => Replay?.Recorder;
private int _checkpointInterval;
private int _checkpointMinInterval;
private int _visualEventThreshold;
public uint? AutoPauseCountdown { get; set; }
public int? ScrubbingTarget { get; set; }
@@ -93,7 +93,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
_initialized = true;
_sawmill = _logMan.GetSawmill("replay");
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
_confMan.OnValueChanged(CVars.CheckpointInterval, (value) => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
_client.RunLevelChanged += OnRunLevelChanged;
}

View File

@@ -1647,15 +1647,20 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<int> ReplaySkipThreshold = CVarDef.Create("replay.skip_threshold", 30);
/// <summary>
/// Minimum number of ticks before a new checkpoint tick is generated (overrides SpawnThreshold and StateThreshold)
/// </summary>
public static readonly CVarDef<int> CheckpointMinInterval = CVarDef.Create("replay.checkpoint_min_interval", 60);
/// <summary>
/// Maximum number of ticks before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 200);
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 500);
/// <summary>
/// Maximum number of entities that can be spawned before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 100);
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 1000);
/// <summary>
/// Maximum number of entity states that can be applied before a new checkpoint tick is generated.