Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix thread_ctrl::wait_for_accurate, use it to improve LV2 Timers #13913

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 23 additions & 9 deletions Utilities/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2396,7 +2396,7 @@ thread_state thread_ctrl::state()
return static_cast<thread_state>(_this->m_sync & 3);
}

void thread_ctrl::wait_for(u64 usec, [[maybe_unused]] bool alert /* true */)
bool thread_ctrl::wait_for(u64 usec, bool alert)
{
auto _this = g_tls_this_thread;

Expand Down Expand Up @@ -2440,23 +2440,33 @@ void thread_ctrl::wait_for(u64 usec, [[maybe_unused]] bool alert /* true */)
timerfd_settime(fd_timer, 0, &timeout, NULL);
if (read(fd_timer, &missed, sizeof(missed)) != sizeof(missed))
sig_log.error("timerfd: read() failed");
return;
return true;
}
#endif

if (_this->m_sync.bit_test_reset(2) || _this->m_taskq)
if ((alert && (_this->m_sync.bit_test_reset(2) || _this->m_taskq)) || _this->m_sync & 1)
{
return;
// Notified or aborted
return false;
}

const atomic_wait_timeout timeout{usec <= 0xffff'ffff'ffff'ffff / 1000 ? usec * 1000 : 0xffff'ffff'ffff'ffff};

if (!alert)
{
_this->m_sync.wait(0, 1, timeout);
return true;
}

// Wait for signal and thread state abort
atomic_wait::list<2> list{};
list.set<0>(_this->m_sync, 0, 4 + 1);
list.set<1>(_this->m_taskq, nullptr);
list.wait(atomic_wait_timeout{usec <= 0xffff'ffff'ffff'ffff / 1000 ? usec * 1000 : 0xffff'ffff'ffff'ffff});
list.wait(timeout);
return true; // Unknown if notified for now
}

void thread_ctrl::wait_for_accurate(u64 usec)
void thread_ctrl::wait_for_accurate(u64 usec, bool alert)
{
if (!usec)
{
Expand All @@ -2481,11 +2491,15 @@ void thread_ctrl::wait_for_accurate(u64 usec)
{
#ifdef __linux__
// Do not wait for the last quantum to avoid loss of accuracy
wait_for(usec - ((usec % host_min_quantum) + host_min_quantum), false);
if (!wait_for(usec - ((usec % host_min_quantum) + host_min_quantum), alert))
#else
// Wait on multiple of min quantum for large durations to avoid overloading low thread cpus
wait_for(usec - (usec % host_min_quantum), false);
if (!wait_for(usec - (usec % host_min_quantum), alert))
#endif
{
// Notified
return;
}
}
// TODO: Determine best value for yield delay
else if (usec >= host_min_quantum / 2)
Expand All @@ -2504,7 +2518,7 @@ void thread_ctrl::wait_for_accurate(u64 usec)
break;
}

usec = (until - current).count();
usec = std::chrono::duration_cast<std::chrono::microseconds>(until - current).count();
}
}

Expand Down
6 changes: 3 additions & 3 deletions Utilities/Thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,11 @@ class thread_ctrl final
// Read current state, possibly executing some tasks
static thread_state state();

// Wait once with timeout. Infinite value is -1.
static void wait_for(u64 usec, bool alert = true);
// Wait once with timeout. Infinite value is -1. (returns false if known to be notified)
static bool wait_for(u64 usec, bool alert = true);

// Waiting with accurate timeout
static void wait_for_accurate(u64 usec);
static void wait_for_accurate(u64 usec, bool alert = false);

// Wait.
static inline void wait()
Expand Down
9 changes: 5 additions & 4 deletions rpcs3/Emu/Cell/Modules/cellAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,9 +613,10 @@ void cell_audio_thread::advance(u64 timestamp)

lock.unlock();

lv2_obj::notify_all_t notify;

for (u32 i = 0; i < queue_count; i++)
{
lv2_obj::notify_all_t notify;
queues[i]->send(event_sources[i], CELL_AUDIO_EVENT_MIX, 0, event_data3[i]);
}
}
Expand Down Expand Up @@ -777,7 +778,7 @@ void cell_audio_thread::operator()()
const s64 audio_period_alignment_delta = (timestamp - m_start_time) % cfg.audio_block_period;
if (audio_period_alignment_delta > cfg.period_comparison_margin)
{
thread_ctrl::wait_for(audio_period_alignment_delta - cfg.period_comparison_margin);
thread_ctrl::wait_for_accurate(audio_period_alignment_delta - cfg.period_comparison_margin, false);
}

if (cfg.buffering_enabled)
Expand Down Expand Up @@ -831,7 +832,7 @@ void cell_audio_thread::operator()()

if (time_left > cfg.period_comparison_margin)
{
thread_ctrl::wait_for(get_thread_wait_delay(time_left));
thread_ctrl::wait_for_accurate(get_thread_wait_delay(time_left), false);
continue;
}
}
Expand Down Expand Up @@ -904,7 +905,7 @@ void cell_audio_thread::operator()()
const s64 time_left = m_dynamic_period - time_since_last_period;
if (time_left > cfg.period_comparison_margin)
{
thread_ctrl::wait_for(get_thread_wait_delay(time_left));
thread_ctrl::wait_for_accurate(get_thread_wait_delay(time_left), false);
continue;
}

Expand Down
6 changes: 3 additions & 3 deletions rpcs3/Emu/Cell/lv2/lv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,7 @@ void lv2_obj::cleanup()

void lv2_obj::schedule_all(u64 current_time)
{
usz notify_later_idx = 0;
usz notify_later_idx = std::basic_string_view<const void*>{g_to_notify, 4}.find_first_of(std::add_pointer_t<const void>{});

if (!g_pending && g_scheduler_ready)
{
Expand All @@ -1663,7 +1663,7 @@ void lv2_obj::schedule_all(u64 current_time)
continue;
}

if (notify_later_idx == std::size(g_to_notify))
if (notify_later_idx >= std::size(g_to_notify))
{
// Out of notification slots, notify locally (resizable container is not worth it)
target->state.notify_one(cpu_flag::signal + cpu_flag::suspend);
Expand Down Expand Up @@ -1697,7 +1697,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 (notify_later_idx == std::size(g_to_notify))
if (notify_later_idx >= std::size(g_to_notify))
{
// Out of notification slots, notify locally (resizable container is not worth it)
target->state.notify_one(cpu_flag::notify);
Expand Down
7 changes: 4 additions & 3 deletions rpcs3/Emu/Cell/lv2/sys_timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ lv2_timer_thread::lv2_timer_thread()
void lv2_timer_thread::operator()()
{
u64 sleep_time = 0;
u64 old_now = umax;

while (true)
{
Expand All @@ -123,7 +124,7 @@ void lv2_timer_thread::operator()()
sleep_time = std::min(sleep_time, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale;
}

thread_ctrl::wait_for(sleep_time);
thread_ctrl::wait_for_accurate(utils::sub_saturate<u64>(sleep_time, get_guest_system_time() - old_now));

if (thread_ctrl::state() == thread_state::aborting)
{
Expand All @@ -138,15 +139,15 @@ void lv2_timer_thread::operator()()
continue;
}

const u64 _now = get_guest_system_time();
old_now = get_guest_system_time();

reader_lock lock(mutex);

for (const auto& timer : timers)
{
while (lv2_obj::check(timer))
{
if (const u64 advised_sleep_time = timer->check(_now))
if (const u64 advised_sleep_time = timer->check(old_now))
{
if (sleep_time > advised_sleep_time)
{
Expand Down