New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 700165 link

Starred by 2 users

Issue metadata

Status: Available
Owner: ----
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Windows
Pri: 3
Type: Feature



Sign in to add a comment

Avoid posting a delayed task for frame deadline when it aligns with next frame (v-sync) to prevent triggering high resolution system timers

Project Member Reported by stanisc@chromium.org, Mar 9 2017

Issue description

On Windows when there is a high resolution delayed task (< 32 ms delay) in the task queue, message loop activates high resoultion system timers, which means the system timer interval goes up from 15.625 ms (64 Hz) to something close to 1 ms (1 KHz).

Here is the related code:

In incoming_task_queue.cc:

#if defined(OS_WIN)
  // We consider the task needs a high resolution timer if the delay is
  // more than 0 and less than 32ms. This caps the relative error to
  // less than 50% : a 33ms wait can wake at 48ms since the default
  // resolution on Windows is between 10 and 15ms.
  if (delay > TimeDelta() &&
      delay.InMilliseconds() < (2 * Time::kMinLowResolutionThresholdMs)) {
    pending_task.is_high_res = true;
  }
#endif

In message_loop.cc:

#if defined(OS_WIN)
  // On Windows we activate the high resolution timer so that the wait
  // _if_ triggered by the timer happens with good resolution. If we don't
  // do this the default resolution is 15ms which might not be acceptable
  // for some tasks.
  bool high_res = pending_high_res_tasks_ > 0;
  if (high_res != in_high_res_mode_) {
    in_high_res_mode_ = high_res;
    Time::ActivateHighResolutionTimer(in_high_res_mode_);
  }
#endif

The problem is that the system timers resolution affects the entire system, all processes. So all processes get interrupted more often if there any sleep timers, and that leads to increased power usage. 

Here is a quote from MSDN (https://msdn.microsoft.com/en-us/library/windows/hardware/jj130809.aspx):
"To extend battery life, the system should configure the lowest frequency possible. The default value for the system timer resolution is 15.6 milliseconds."

So to avoid increasing the system timers frequency Chrome should avoid scheduling high resolution tasks as much as possible.
The most common high resolution tasks are the following three:

1) A task created by DelayBasedTimeSource to generate a software v-sync signal. This is consumed by DelayBasedBeginFrameSource and further, as a BeginFrame signal, by cc::Scheduler and cc::DisplayScheduler.
I've landed an optional (disabled by default) GPU VSync feature that replaces software v-sync with a hardware v-sync. Enabling that gets rid of this task.

2) A task created by DisplayScheduler to trigger a frame deadline:

  base::TimeDelta delta =
      std::max(base::TimeDelta(), desired_deadline - base::TimeTicks::Now());
  task_runner_->PostDelayedTask(FROM_HERE,
                                begin_frame_deadline_task_.callback(), delta);

3) A similar task created by Scheduler for the same purpose as #2:

  base::TimeDelta delta = std::max(deadline - Now(), base::TimeDelta());
  task_runner_->PostDelayedTask(
      FROM_HERE, begin_impl_frame_deadline_task_.callback(), delta);

In case of #2 and #3 the target task time (deadline) seems to be aligned with the next frame time most of the time, which means the deadline is usually the same as the curret frame time plus the current frame interval. Which makes me think that the deadline task shouldn't be necessary most of the time. Instead the scheduler could use the next BeginFrame signal to trigger a deadline if that is still necessary. In fact the existing logic in both classes already seems to be doing that. The reason for that is that the delayed task execution time isn't precise and it could run after the next BeginFrame signal already.

I prototyped a simple change where the two tasks above are simply not posted if their deadline time matches the next frame time. That might not be entirely correct in all corner cases but seems to be sufficient for a few test pages I tried the prototype with. I verified that with the prototype the system timer frequency remains at low resolution 64 Hz. Of course, that depends on whether there are other high resolution tasks, but at least for some scenarios like video playback getting rid of the 3 high resolution tasks mentioned above seems to be sufficient.

The power saving seems to be around 0.2-0.25 Wt compared to 4-5 Wt overall power usage for the couple scenarios that I tried so far. That is around 5%. This is based on estimated power usage as reported by Intel Power Gadget. I tested this on Windows 10 Dell XPS laptop. This is not precise because the power reported by the gadget seems to be somewhat unstable. I haven't tried testing this with a BattOr device. I'll try that later and report results separately.

 
Cc: briander...@chromium.org skyos...@chromium.org eseckler@chromium.org
Cc: enne@chromium.org
Interesting. I believe that |deadline == args.time + interval| for the tasks in #2 and #3 only happens if we're in a LATE deadline mode in the Scheduler or are blocked on something in the DisplayScheduler. In that case, we could probably skip posting the deadline task as you suggested. We may want to check that we'll actually receive the next BeginFrame message (i.e., |observing_begin_frame_source_ == true|), but this should usually be the case.

There are a few corner cases where we won't receive another BeginFrame until the prior one was acknowledged, though: BackToBackBeginFrameSource does this, and we're planning to do something similar for headless chrome.

In such cases, it might be possible that the Scheduler and/or DisplayScheduler never respond because they are waiting for damage but don't receive any. Once we change to BeginFrameAck-based early deadlines in both DisplayScheduler and Scheduler ( https://crbug.com/697086 ), this shouldn't be a problem anymore - The plan is to trigger an early deadline as soon as we know that nobody has updates.
eseckler@, thanks for the comment! 
My impression from debugging the current code is that deadlines are scheduled twice for each frame:
1) First a LATE deadline is set by creating a delayed task that aligns with next BeginFrame
2) Then a few ms later an IMMEDIATE deadline is scheduled which overrides the previous LATE deadline and cancels the previous deadline task. However the actual pending task remains in the delayed task queue and still wakes up the thread at the target time. It is just the task's callback that gets reset.

Will changing to BeginFrameAck-based early deadlines result in not creating those delayed tasks at all?
> Will changing to BeginFrameAck-based early deadlines result in not creating those delayed tasks at all?

No, I don't think BeginFrameAcks will change that.

Irrespective of that, is it possible to cancel the actual task that was submitted to TaskRunner so that we don't wake up later, rather than resetting the callback? Not sure if TaskRunner supports this.
> Irrespective of that, is it possible to cancel the actual task that was 
> submitted to TaskRunner so that we don't wake up later, rather than resetting 
> the callback? Not sure if TaskRunner supports this.

Yes, kind of, see https://cs.chromium.org/chromium/src/base/cancelable_callback.h

Currently the base MessageLoop doesn't track this so the OS wakeup isn't cancelled (and in some cases can't be), but this is being improved. I think we should at least be able to avoid going to hi-res timer mode.
Status: Available (was: Untriaged)
Owner: stanisc@chromium.org
Status: Assigned (was: Available)
I'll take this.
Any though on trying to implement this the way I did in the prototype (i.e. not posting a deadline task if is target time is going to align with a next BeginFrame) vs. trying to do a more general approach that would apply to all tasks.

I was thinking that perhaps the v-sync could be fed into the message pump or whatever is going to replace it in LL. Then we could look at the target task time and decide whether to enter a sleep or just wait for the next v-sync signal. And if waiting for the next v-sync we wouldn't need to raise the system time frequency. This shouldn't require a significant change in the message pump logic.

Thoughts?

And FYI, I've just done another test comparing the current implementation vs. the prototype (actually the prototype mentioned above plus GPU V-sync to ensure that there are no delayed tasks with a short delay). The test used
a website without ads that produces 60 FPS (Blur Busters Motion Tests).

           Idle wakeups / sec  CPU (per core)   Power (estimated)
-----------------------------------------------------------
Baseline   2636                89.20%           2.74 Wt
Prototype  2436                84.05%           2.62 Wt
Starting with a simple implementation that handles just some cases seems like a reasonable plan.

I think we ultimately want some sort of master control system that looks at battery status and other factors to decide how much it is willing to adjust task target times in order to coalesce timers to existing wakeups, but starting with a smaller simpler version seems appropriate, especially since that should let us ship initial improvements earlier.
Right, just fixing the cc scheduler should cover most of these cases. Random components shouldn't really be even trying to post vsync-"aligned" delayed tasks. Instead they should be subscribing to BeginFrame notifications from cc. 
Yeah, by v-sync aligned I really meant BeginFrame aligned. V-sync signal is consumed pretty much only by a BeginFrameSource and the rest of components see only OnBeginFrame produced by BFS.
Cc: sunn...@chromium.org
+ sunnyps@chromium.org

It seems my prototype doesn't work anymore after recent changes in scheduler.cc. If I try to skip posting a deadline task all progress seems to halt as there seem to be expectation for that task.

Sunny, could you recommend a solution? What I am trying to achieve is to not post a task that is going to be scheduled exactly on next BeginFrame (i.e. last frame begin time + last frame interval) because that is redundant and contributes to raising system timer frequency. 

I'm probably going to revert the change I made that broke your CL. That change was made as a speculative fix for an input latency regression (issue 702372) but it didn't have any effect.
I was ready to submit the fix for a code review when I realized that my change breaks a large number of tests in SchedulerTests.

SchedulerTests setup provides a very fine grained control on when each BeginFrame is fired by calling AdvanceFrame() and when the deadline task should run by spinning the message loop.

With my change in effect, the deadline task is skipped if aligned exactly with the next BeginFrame. So spinning the message loop does nothing. As a result bunch of test code assumptions are failed.

I spent some time trying to patch the test code to work with the implicit deadline but quickly realized that that would require some pretty substantial changes in 20+ test cases. With the implicit deadline that in-between state where the deadline has already arrived but the next BeginFrame hasn't is impossible in most cases so fixing some tests would require removing some code that tests that state.

Let me know if you think it worth spending time trying to make all these tests work as opposed to abandoning this approach and trying to solve this problem at a higher level (i.e. by coalescing task execution with BeginFrame signal, which would require a special task runner / message pump).

Just to reiterate, the eventual goal is to reduce the number of idle wake-ups and to avoid having to raise the system timer frequency (which we have to do to improve accuracy of delayed tasks wake-ups).

Here is the work in progress change: https://chromium-review.googlesource.com/c/565581/
I've faced similar issues when trying to make changes to the deadline logic. In the past I've worked around test failures by changing AdvanceFrame/FrameHasNotAdvancedCallback.

I think the main issue is that our tests are brittle. We shouldn't overengineer the solution at a higher level just because the tests are bad. We should change the tests and this is mostly a responsibility of us scheduler owners.
Agreed that we should change the tests rather than the feature. An option that comes to mind (which I'm not crazy about however) is to make the feature optional and make those tests disable it. Alternatively we could a new callback where the test can inspect intermediate state.
Cc: -stanisc@chromium.org
Owner: ----
Status: Untriaged (was: Assigned)
Making this visible to triage.

Here are WIP changes for this - one for Scheduler and another for DisplayScheduler.

[1] https://chromium-review.googlesource.com/c/chromium/src/+/565581
[2] https://chromium-review.googlesource.com/c/chromium/src/+/571626

The further progress was blocked on adding / updating unit tests.
I'm curious what work needs to be done so we can triage this better? Stan - it looks like you've got most of this.
Stan, isn't on Chrome any more. I have looked at this in the past so I can own this for now. However, I don't plan on doing this any time soon.
Status: Available (was: Untriaged)
This would be a very nice thing to have.
Labels: -Type-Bug Type-Feature

Sign in to add a comment