Bug 1826224: Enable high-precision timers on Windows for foreground processes when not on battery power r=smaug

Initially controlled by a pref and only enabled by default in Nightly.

Differential Revision: https://phabricator.services.mozilla.com/D189555
This commit is contained in:
Justin Link 2023-10-31 14:34:39 +00:00
parent 380463fef9
commit 4a6beb6b60
3 changed files with 119 additions and 3 deletions

View file

@ -14423,6 +14423,19 @@
value: 10000.0
mirror: always
#ifdef XP_WIN
# Controls whether or not TimerThread will automatically increase the Windows timer
# resolution when appropriate conditions are met.
- name: timer.auto_increase_timer_resolution
type: RelaxedAtomicBool
#ifdef NIGHTLY_BUILD
value: true
#else
value: false
#endif
mirror: always
#endif
#---------------------------------------------------------------------------
# Prefs starting with "toolkit."
#---------------------------------------------------------------------------

View file

@ -11,6 +11,7 @@
#include "nsThreadUtils.h"
#include "nsIObserverService.h"
#include "nsIPropertyBag2.h"
#include "mozilla/Services.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/ArenaAllocator.h"
@ -24,6 +25,44 @@
using namespace mozilla;
#ifdef XP_WIN
// Include Windows header required for enabling high-precision timers.
# include <windows.h>
# include <mmsystem.h>
static constexpr UINT kTimerPeriodHiRes = 1;
static constexpr UINT kTimerPeriodLowRes = 16;
// Helper functions to determine what Windows timer resolution to target.
static constexpr UINT GetDesiredTimerPeriod(const bool aOnBatteryPower,
const bool aLowProcessPriority) {
const bool useLowResTimer = aOnBatteryPower || aLowProcessPriority;
return useLowResTimer ? kTimerPeriodLowRes : kTimerPeriodHiRes;
}
static_assert(GetDesiredTimerPeriod(true, false) == kTimerPeriodLowRes);
static_assert(GetDesiredTimerPeriod(false, true) == kTimerPeriodLowRes);
static_assert(GetDesiredTimerPeriod(true, true) == kTimerPeriodLowRes);
static_assert(GetDesiredTimerPeriod(false, false) == kTimerPeriodHiRes);
UINT TimerThread::ComputeDesiredTimerPeriod() const {
const bool lowPriorityProcess =
mCachedPriority.load(std::memory_order_relaxed) <
hal::PROCESS_PRIORITY_FOREGROUND;
// NOTE: Using short-circuiting here to avoid call to GetSystemPowerStatus()
// when we know that that result will not affect the final result. (As
// confirmed by the static_assert's above, onBatteryPower does not affect the
// result when the lowPriorityProcess is true.)
SYSTEM_POWER_STATUS status;
const bool onBatteryPower = !lowPriorityProcess &&
GetSystemPowerStatus(&status) &&
(status.ACLineStatus == 0);
return GetDesiredTimerPeriod(onBatteryPower, lowPriorityProcess);
}
#endif
// Uncomment the following line to enable runtime stats during development.
// #define TIMERS_RUNTIME_STATS
@ -175,6 +214,8 @@ TimerObserverRunnable::Run() {
false);
observerService->AddObserver(mObserver, "resume_process_notification",
false);
observerService->AddObserver(mObserver, "ipc:process-priority-changed",
false);
}
return NS_OK;
}
@ -720,6 +761,25 @@ TimerThread::Run() {
AutoTArray<uint64_t, kMaxQueuedTimerFired> queuedTimersFiredPerWakeup;
queuedTimersFiredPerWakeup.SetLengthAndRetainStorage(kMaxQueuedTimerFired);
#ifdef XP_WIN
// kTimerPeriodEvalIntervalSec is the minimum amount of time that must pass
// before we will consider changing the timer period again.
static constexpr float kTimerPeriodEvalIntervalSec = 2.0f;
const TimeDuration timerPeriodEvalInterval =
TimeDuration::FromSeconds(kTimerPeriodEvalIntervalSec);
TimeStamp nextTimerPeriodEval = TimeStamp::Now() + timerPeriodEvalInterval;
// If this is false, we will perform all of the logic but will stop short of
// actually changing the timer period.
const bool adjustTimerPeriod =
StaticPrefs::timer_auto_increase_timer_resolution();
UINT lastTimePeriodSet = ComputeDesiredTimerPeriod();
if (adjustTimerPeriod) {
timeBeginPeriod(lastTimePeriodSet);
}
#endif
uint64_t timersFiredThisWakeup = 0;
while (!mShutdown) {
// Have to use PRIntervalTime here, since PR_WaitCondVar takes it
@ -742,6 +802,20 @@ TimerThread::Run() {
waitFor = TimeDuration::Forever();
TimeStamp now = TimeStamp::Now();
#ifdef XP_WIN
if (now >= nextTimerPeriodEval) {
const UINT newTimePeriod = ComputeDesiredTimerPeriod();
if (newTimePeriod != lastTimePeriodSet) {
if (adjustTimerPeriod) {
timeEndPeriod(lastTimePeriodSet);
timeBeginPeriod(newTimePeriod);
}
lastTimePeriodSet = newTimePeriod;
}
nextTimerPeriodEval = now + timerPeriodEvalInterval;
}
#endif
#if TIMER_THREAD_STATISTICS
if (!mNotified && !mIntendedWakeupTime.IsNull() &&
now < mIntendedWakeupTime) {
@ -915,6 +989,13 @@ TimerThread::Run() {
queuedTimersFiredPerWakeup);
}
#ifdef XP_WIN
// About to shut down - let's finish off the last time period that we set.
if (adjustTimerPeriod) {
timeEndPeriod(lastTimePeriodSet);
}
#endif
return NS_OK;
}
@ -1284,8 +1365,18 @@ void TimerThread::DoAfterSleep() {
}
NS_IMETHODIMP
TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
const char16_t* /* aData */) {
TimerThread::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (strcmp(aTopic, "ipc:process-priority-changed") == 0) {
nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
MOZ_ASSERT(props != nullptr);
int32_t priority = static_cast<int32_t>(hal::PROCESS_PRIORITY_UNKNOWN);
props->GetPropertyAsInt32(u"priority"_ns, &priority);
mCachedPriority.store(static_cast<hal::ProcessPriority>(priority),
std::memory_order_relaxed);
}
if (StaticPrefs::timer_ignore_sleep_wake_notifications()) {
return NS_OK;
}

View file

@ -17,6 +17,7 @@
#include "nsTArray.h"
#include "mozilla/Attributes.h"
#include "mozilla/HalTypes.h"
#include "mozilla/Monitor.h"
#include "mozilla/ProfilerUtils.h"
@ -75,6 +76,14 @@ class TimerThread final : public mozilla::Runnable, public nsIObserver {
void PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
MOZ_REQUIRES(mMonitor);
// Using atomic because this value is written to in one place, and read from
// in another, and those two locations are likely to be executed from separate
// threads. Reads/writes to an aligned value this size should be atomic even
// without using std::atomic, but doing this explicitly provides a good
// reminder that this is accessed from multiple threads.
std::atomic<mozilla::hal::ProcessPriority> mCachedPriority =
mozilla::hal::PROCESS_PRIORITY_UNKNOWN;
nsCOMPtr<nsIThread> mThread;
// Lock ordering requirements:
// (optional) ThreadWrapper::sMutex ->
@ -171,6 +180,10 @@ class TimerThread final : public mozilla::Runnable, public nsIObserver {
TimeDuration minDelay,
TimeDuration maxDelay) const;
#ifdef XP_WIN
UINT ComputeDesiredTimerPeriod() const;
#endif
#ifdef DEBUG
// Checks mTimers to see if any entries are out of order or any cached
// timeouts are incorrect and will assert if any inconsistency is found. Has
@ -231,5 +244,4 @@ class TimerThread final : public mozilla::Runnable, public nsIObserver {
void PrintStatistics() const;
#endif
};
#endif /* TimerThread_h___ */