Configured the garbage collector for lower spikes (#88)

Did you know that the garbage collector's default settings are designed for single-threaded applications and quick start-up times? We don't care about either of these.

Configured:
- Concurrent GC (default is true) (set for clarity)
- RetainVM: segments that should be deleted are put on a standby list for future use (default is false)
- QuickJit: enabling quick JIT decreases startup time but can produce code with degraded performance characteristics; for example, the code may use more stack space, allocate more memory, and run slower. (default is true) (disabled)
- ReadyToRun: configures whether the .NET runtime uses pre-compiled code for images with available ReadyToRun data; disabling this option forces the runtime to JIT-compile framework code. (default is true) (set to false, we dont publish with this option anyway)
- TieredPGO: this setting enables dynamic (tiered) profile-guided optimization (PGO) in .NET 6 and later versions. If quick JIT is disabled but tiered compilation is enabled, only pre-compiled code participates in tiered compilation. If a method is not pre-compiled with ReadyToRun, the JIT behavior is the same as if tiered compilation were disabled.

Features:
- Set ``GCLatencyMode.Interactive`` when in-menu and emulator is paused, otherwise uses ``GCLatencyMode.LowLatency``.
- Added a new UI option in the Settings > CPU menu to toggle ``GCLatencyMode.LowLatency`` during guest runtime.

![image](/attachments/84ffc6c6-d92c-4ec5-8a95-9d72dc6f1b04)

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/88
This commit is contained in:
Max
2026-05-22 23:12:19 +00:00
committed by sh0inx
parent e477ec7149
commit 4631e0a9e1
12 changed files with 127 additions and 4 deletions

View File

@@ -46,6 +46,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -579,6 +580,12 @@ namespace Ryujinx.Ava.Systems
{
_isActive = false;
_playTimer.Stop();
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
}
private void Exit()
@@ -662,6 +669,12 @@ namespace Ryujinx.Ava.Systems
_chrono.Stop();
_playTimer.Stop();
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
}
public void DisposeGpu()
@@ -915,7 +928,14 @@ namespace Ryujinx.Ava.Systems
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
appMetadata => appMetadata.UpdatePreGame()
);
_playTimer.Start();
if (ConfigurationState.Instance.System.GCLowLatency)
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
Logger.Info?.Print(LogClass.Application, "Garbage collector set to low latency mode.");
}
}
internal void Resume()
@@ -926,6 +946,12 @@ namespace Ryujinx.Ava.Systems
_playTimer.Start();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed.");
if (ConfigurationState.Instance.System.GCLowLatency)
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
Logger.Info?.Print(LogClass.Application, "Garbage collector set to low latency mode.");
}
}
internal void Pause()
@@ -936,6 +962,12 @@ namespace Ryujinx.Ava.Systems
_playTimer.Stop();
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused.");
GCSettings.LatencyMode = GCLatencyMode.Interactive;
if (ConfigurationState.Instance.System.GCLowLatency)
{
Logger.Info?.Print(LogClass.Application, "Garbage collector set to interactive mode.");
}
}
private void InitEmulatedSwitch()

View File

@@ -470,6 +470,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// Uses Hypervisor over JIT if available
/// </summary>
public bool UseHypervisor { get; set; }
/// <summary>
/// Enable or disable low-latency mode for garbage collection
/// </summary>
public bool GCLowLatency { get; set; }
/// <summary>
/// Enables or disables the GDB stub

View File

@@ -112,6 +112,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
System.UseHypervisor.Value = cff.UseHypervisor;
System.GCLowLatency.Value = cff.GCLowLatency;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
@@ -535,7 +536,8 @@ namespace Ryujinx.Ava.Systems.Configuration
{
if (cff.AudioBackend is AudioBackend.SDL2)
cff.AudioBackend = AudioBackend.SDL3;
})
}),
(72, static cff => cff.GCLowLatency = false)
);
}
}

View File

@@ -417,6 +417,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// Uses Hypervisor over JIT if available
/// </summary>
public ReactiveObject<bool> UseHypervisor { get; private set; }
/// <summary>
/// Enable or disable low-latency garbage collection
/// </summary>
public ReactiveObject<bool> GCLowLatency { get; private set; }
public SystemSection()
{
@@ -471,6 +476,8 @@ namespace Ryujinx.Ava.Systems.Configuration
AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>();
UseHypervisor.LogChangesToValue(nameof(UseHypervisor));
GCLowLatency = new ReactiveObject<bool>();
GCLowLatency.LogChangesToValue(nameof(GCLowLatency));
}
}

View File

@@ -87,6 +87,7 @@ namespace Ryujinx.Ava.Systems.Configuration
IgnoreApplet = System.IgnoreControllerApplet,
SkipUserProfiles = System.SkipUserProfilesManager,
UseHypervisor = System.UseHypervisor,
GCLowLatency = System.GCLowLatency,
GuiColumns = new GuiColumns
{
FavColumn = UI.GuiColumns.FavColumn,