Stop paying per-packet and per-argument costs in the FIFO loop

Three changes to the same loop, all measured against 36728 packets and 48737
dispatches per frame in Sonic '06.

GET is published on a bounded lag rather than every packet. It is a release
store into guest DMA memory, and get shares a 64-byte line with put which the
guest PPU writes from another CPU cluster, so each publish was a cross-cluster
coherence miss. The guest reads GET to size its free ring space and is far ahead
of us here, FIFO stalls measuring 0.1 a frame, so lag is invisible to it.
Anything that can idle or block publishes immediately: the put wait in inc_get,
the NOP path, and set_get.

The FIFO accuracy setting is snapshotted once per packet instead of being read
per argument. Reading it goes through a seq_cst atomic load, which on ARM64 is
an ldar the compiler cannot hoist out of the loop.

The again poll is relaxed. That flag is only ever set by this thread, by the
handler invoked immediately before, so sequential consistency buys nothing and
cost another ldar per dispatch.
This commit is contained in:
jpolo1224
2026-08-10 00:43:34 -04:00
parent 1c371cfad7
commit 5636c9f3ff
2 changed files with 37 additions and 4 deletions
+26 -4
View File
@@ -37,6 +37,21 @@ namespace rsx
void FIFO_control::sync_get() const
{
// Every 8th packet. The guest reads GET to work out how much ring space is
// free, and it is far ahead of us here (FIFO stalls measure 0.1/frame), so a
// bounded lag is invisible to it. Anything that can idle or block publishes
// immediately via sync_get_force so a waiting producer is never held up.
if (++m_get_sync_counter & 7)
{
return;
}
m_ctrl->get.release(m_internal_get);
}
void FIFO_control::sync_get_force() const
{
m_get_sync_counter = 0;
m_ctrl->get.release(m_internal_get);
}
@@ -58,7 +73,7 @@ namespace rsx
{
// NOTE: Only supposed to be invoked to wait for a single arg on command[0] (4 bytes)
// Wait for put to allow us to procceed execution
sync_get();
sync_get_force();
invalidate_cache();
while (read_put() == m_internal_get && !Emu.IsStopped())
@@ -257,7 +272,7 @@ namespace rsx
return {};
}
if (g_cfg.core.rsx_fifo_accuracy)
if (m_accurate_fetch)
{
// Return a pointer to the cache storage with confined access
const u32 cache_offset_in_words = (m_internal_get - m_cache_addr) / 4;
@@ -365,6 +380,8 @@ namespace rsx
void FIFO_control::read(register_pair& data)
{
m_accurate_fetch = !!g_cfg.core.rsx_fifo_accuracy;
if (m_remaining_commands)
{
// Previous block aborted to wait for PUT pointer
@@ -391,7 +408,7 @@ namespace rsx
m_memwatch_cmp = 0;
}
if (!g_cfg.core.rsx_fifo_accuracy) [[ likely ]]
if (!m_accurate_fetch) [[ likely ]]
{
const u32 put = read_put();
@@ -684,6 +701,9 @@ namespace rsx
performance_counters.state = FIFO::state::nop;
}
// Going idle: publish GET now rather than carrying up to seven packets of
// lag into a period where the producer may be waiting on ring space.
fifo_ctrl->sync_get_force();
return;
}
case FIFO::FIFO_EMPTY:
@@ -925,7 +945,9 @@ namespace rsx
{
method(m_ctx, reg, value);
if (state & cpu_flag::again)
// Relaxed: `again` is only set by this thread, by the handler just called.
// The seq_cst default is an ldar on ARM64 for no benefit here.
if (state.observe() & cpu_flag::again)
{
m_ctx->register_state->decode(reg, m_ctx->register_state->latch);
break;
+11
View File
@@ -190,7 +190,18 @@ namespace rsx
u32 get_pos() const { return m_internal_get; }
u32 last_cmd() const { return m_cmd; }
// Publishing GET is a release store into guest DMA memory, and `get` shares a
// 64-byte line with `put` which the guest PPU writes from another CPU cluster. At
// ~36700 packets/frame that is a cross-cluster coherence miss per packet. Bounded
// lag instead, with sync_get_force on every path that can idle or block.
void sync_get() const;
void sync_get_force() const;
mutable u32 m_get_sync_counter = 0;
// Snapshot of g_cfg.core.rsx_fifo_accuracy, refreshed once per packet. Reading the
// config goes through a seq_cst atomic load, an ldar on ARM64 the compiler cannot
// hoist, and it was consulted once per FIFO argument.
bool m_accurate_fetch = false;
std::span<const u32> get_current_arg_ptr(u32 length_in_words) const;
u32 get_remaining_args_count() const { return m_remaining_commands; }
void restore_state(u32 cmd, u32 count);