Skip to content

Commit

Permalink
LV2: Introduce Dynamic Timer signals
Browse files Browse the repository at this point in the history
  • Loading branch information
elad335 committed Dec 30, 2024
1 parent b16d267 commit dd3f27a
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 23 deletions.
138 changes: 122 additions & 16 deletions rpcs3/Emu/Cell/lv2/lv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,8 @@ static std::deque<class cpu_thread*> g_to_sleep;
static atomic_t<bool> g_scheduler_ready = false;
static atomic_t<u64> s_yield_frequency = 0;
static atomic_t<u64> s_max_allowed_yield_tsc = 0;
static atomic_t<u64> s_lv2_timers_sum_of_ten_delay_in_us = 5000;
static atomic_t<u64> s_lv2_timers_min_timer_in_us = 0;
static u64 s_last_yield_tsc = 0;
atomic_t<u32> g_lv2_preempts_taken = 0;

Expand Down Expand Up @@ -1432,7 +1434,7 @@ bool lv2_obj::awake(cpu_thread* thread, s32 prio)

if (!g_postpone_notify_barrier)
{
notify_all();
notify_all(thread);
}

return result;
Expand Down Expand Up @@ -1573,6 +1575,11 @@ bool lv2_obj::sleep_unlocked(cpu_thread& thread, u64 timeout, u64 current_time)
{
if (it == end || it->first > wait_until)
{
if (it == g_waiting.cbegin())
{
s_lv2_timers_min_timer_in_us.release(wait_until);
}

g_waiting.emplace(it, wait_until, &thread);
break;
}
Expand Down Expand Up @@ -1835,6 +1842,8 @@ void lv2_obj::cleanup()
g_waiting.clear();
g_pending = 0;
s_yield_frequency = 0;
s_lv2_timers_sum_of_ten_delay_in_us = 5000;
s_lv2_timers_min_timer_in_us = 0;
}

void lv2_obj::schedule_all(u64 current_time)
Expand Down Expand Up @@ -1876,7 +1885,7 @@ void lv2_obj::schedule_all(u64 current_time)
}

// Check registered timeouts
while (!g_waiting.empty())
while (!g_waiting.empty() && it != std::end(g_to_notify))
{
const auto pair = &g_waiting.front();

Expand All @@ -1896,15 +1905,7 @@ void lv2_obj::schedule_all(u64 current_time)
ensure(!target->state.test_and_set(cpu_flag::notify));

// Otherwise notify it to wake itself
if (it == std::end(g_to_notify))
{
// Out of notification slots, notify locally (resizable container is not worth it)
target->state.notify_one();
}
else
{
*it++ = &target->state;
}
*it++ = &target->state;
}
}
else
Expand Down Expand Up @@ -2171,7 +2172,36 @@ bool lv2_obj::wait_timeout(u64 usec, ppu_thread* cpu, bool scale, bool is_usleep
#endif
// TODO: Tune for other non windows operating sytems

if (g_cfg.core.sleep_timers_accuracy < (is_usleep ? sleep_timers_accuracy_level::_usleep : sleep_timers_accuracy_level::_all_timers))
const sleep_timers_accuracy_level accuarcy_type = g_cfg.core.sleep_timers_accuracy;
const u64 avg_delay = get_avg_timer_reponse_delay();

static atomic_t<u64> g_success = 0;
static atomic_t<u64> g_fails = 0;

if (accuarcy_type == sleep_timers_accuracy_level::_dynamic && avg_delay < 30 && avg_delay < (remaining + 15) / 2)
{
wait_for(remaining);

if (remaining < host_min_quantum)
{
g_success += remaining;
//g_success++;
}

passed = get_system_time() - start_time;


continue;
}
else
{
if (remaining < host_min_quantum)
{
g_fails += remaining;
//g_fails++;
}
}
if (g_cfg.core.sleep_timers_accuracy < (is_usleep ? sleep_timers_accuracy_level::_dynamic : sleep_timers_accuracy_level::_all_timers))
{
wait_for(remaining);
}
Expand Down Expand Up @@ -2222,7 +2252,7 @@ void lv2_obj::prepare_for_sleep(cpu_thread& cpu)
cpu_counter::remove(&cpu);
}

void lv2_obj::notify_all() noexcept
void lv2_obj::notify_all(cpu_thread* woke_thread) noexcept
{
for (auto cpu : g_to_notify)
{
Expand Down Expand Up @@ -2258,13 +2288,11 @@ void lv2_obj::notify_all() noexcept
return;
}

if (cpu->get_class() != thread_class::spu && cpu->state.none_of(cpu_flag::suspend))
if (cpu->get_class() == thread_class::ppu && cpu->state.none_of(cpu_flag::suspend + cpu_flag::signal))
{
return;
}

std::optional<vm::writer_lock> lock;

constexpr usz total_waiters = std::size(spu_thread::g_spu_waiters_by_value);

u32 notifies[total_waiters]{};
Expand Down Expand Up @@ -2346,4 +2374,82 @@ void lv2_obj::notify_all() noexcept
vm::reservation_notifier_notify(addr);
}
}

if (woke_thread == cpu)
{
return;
}

const u64 min_timer = s_lv2_timers_min_timer_in_us;
const u64 current_time = get_guest_system_time();

if (current_time < min_timer)
{
return;
}

atomic_bs_t<cpu_flag>* notifies_cpus[16];
usz count_notifies_cpus = 0;

std::unique_lock lock(g_mutex, std::try_to_lock);

if (!lock)
{
// Not only is that this method is an opportunistic optimization
// But if it's already locked than it is likely that soon another thread would do this check instead
return;
}

// Do it BEFORE clearing the queue in order to measure the delay properly even if the sleeping thread notified itself
// This 'redundancy' is what allows proper measurements
if (u64 min_time2 = s_lv2_timers_min_timer_in_us; current_time >= min_time2)
{
const u64 sum = s_lv2_timers_sum_of_ten_delay_in_us.observe();
s_lv2_timers_sum_of_ten_delay_in_us.release(sum - sum / 10 + (current_time - min_time2) / 10);
}

// Check registered timeouts
while (!g_waiting.empty() && count_notifies_cpus < std::size(notifies_cpus))
{
const auto pair = &g_waiting.front();

if (pair->first <= current_time)
{
const auto target = pair->second;
g_waiting.pop_front();

if (target != cpu)
{
// Change cpu_thread::state for the lightweight notification to work
ensure(!target->state.test_and_set(cpu_flag::notify));
notifies_cpus[count_notifies_cpus++] = &target->state;
}
}
else
{
// The list is sorted so assume no more timeouts
break;
}
}

if (g_waiting.empty())
{
s_lv2_timers_min_timer_in_us.release(u64{umax});
}
else
{
s_lv2_timers_min_timer_in_us.release(g_waiting.front().first);
}

lock.unlock();

for (usz i = count_notifies_cpus - 1; i != umax; i--)
{
atomic_wait_engine::notify_one(notifies_cpus[i]);
}
}

u64 lv2_obj::get_avg_timer_reponse_delay()
{
return s_lv2_timers_sum_of_ten_delay_in_us / 10;
}
4 changes: 3 additions & 1 deletion rpcs3/Emu/Cell/lv2/sys_sync.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,13 @@ struct lv2_obj

static bool wait_timeout(u64 usec, ppu_thread* cpu = {}, bool scale = true, bool is_usleep = false);

static void notify_all() noexcept;
static void notify_all(cpu_thread* woke_thread = nullptr) noexcept;

// Can be called before the actual sleep call in order to move it out of mutex scope
static void prepare_for_sleep(cpu_thread& cpu);

static u64 get_avg_timer_reponse_delay();

struct notify_all_t
{
notify_all_t() noexcept
Expand Down
2 changes: 2 additions & 0 deletions rpcs3/Emu/RSX/RSXThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,11 +870,13 @@ namespace rsx
{
// Wait 16ms during emulation pause. This reduces cpu load while still giving us the chance to render overlays.
do_local_task(rsx::FIFO::state::paused);
lv2_obj::notify_all();
thread_ctrl::wait_on(state, old, 16000);
}
else
{
on_semaphore_acquire_wait();
lv2_obj::notify_all();
std::this_thread::yield();
}
}
Expand Down
6 changes: 1 addition & 5 deletions rpcs3/Emu/system_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,7 @@ struct cfg_root : cfg::node
cfg::uint<0, (1 << 6) - 1> spu_wakeup_delay_mask{ this, "SPU Wake-Up Delay Thread Mask", (1 << 6) - 1, true };
cfg::uint<0, 400> max_cpu_preempt_count_per_frame{ this, "Max CPU Preempt Count", 0, true };
cfg::_bool allow_rsx_cpu_preempt{ this, "Allow RSX CPU Preemptions", true, true };
#if defined (__linux__) || defined (__APPLE__)
cfg::_enum<sleep_timers_accuracy_level> sleep_timers_accuracy{ this, "Sleep Timers Accuracy", sleep_timers_accuracy_level::_as_host, true };
#else
cfg::_enum<sleep_timers_accuracy_level> sleep_timers_accuracy{ this, "Sleep Timers Accuracy", sleep_timers_accuracy_level::_usleep, true };
#endif
cfg::_enum<sleep_timers_accuracy_level> sleep_timers_accuracy{ this, "Sleep Timers Accuracy 2", sleep_timers_accuracy_level::_dynamic, true };
cfg::_int<-1000, 1500> usleep_addend{ this, "Usleep Time Addend", 0, true };

cfg::uint64 perf_report_threshold{this, "Performance Report Threshold", 500, true}; // In µs, 0.5ms = default, 0 = everything
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Emu/system_config_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ void fmt_class_string<sleep_timers_accuracy_level>::format(std::string& out, u64
switch (value)
{
case sleep_timers_accuracy_level::_as_host: return "As Host";
case sleep_timers_accuracy_level::_dynamic: return "Dynamic";
case sleep_timers_accuracy_level::_usleep: return "Usleep Only";
case sleep_timers_accuracy_level::_all_timers: return "All Timers";
}
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Emu/system_config_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum class spu_block_size_type
enum class sleep_timers_accuracy_level
{
_as_host,
_dynamic,
_usleep,
_all_timers,
};
Expand Down
1 change: 1 addition & 0 deletions rpcs3/rpcs3qt/emu_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_
switch (static_cast<sleep_timers_accuracy_level>(index))
{
case sleep_timers_accuracy_level::_as_host: return tr("As Host", "Sleep timers accuracy");
case sleep_timers_accuracy_level::_dynamic: return tr("Dynamic", "Sleep timers accuracy");
case sleep_timers_accuracy_level::_usleep: return tr("Usleep Only", "Sleep timers accuracy");
case sleep_timers_accuracy_level::_all_timers: return tr("All Timers", "Sleep timers accuracy");
}
Expand Down
2 changes: 1 addition & 1 deletion rpcs3/rpcs3qt/emu_settings_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
{ emu_settings_type::SPUCache, { "Core", "SPU Cache"}},
{ emu_settings_type::DebugConsoleMode, { "Core", "Debug Console Mode"}},
{ emu_settings_type::MaxSPURSThreads, { "Core", "Max SPURS Threads"}},
{ emu_settings_type::SleepTimersAccuracy, { "Core", "Sleep Timers Accuracy"}},
{ emu_settings_type::SleepTimersAccuracy, { "Core", "Sleep Timers Accuracy 2"}},
{ emu_settings_type::ClocksScale, { "Core", "Clocks scale"}},
{ emu_settings_type::AccuratePPU128Loop, { "Core", "Accurate PPU 128-byte Reservation Op Max Length"}},
{ emu_settings_type::PerformanceReport, { "Core", "Enable Performance Report"}},
Expand Down

0 comments on commit dd3f27a

Please sign in to comment.