Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Unified Diff: base/message_loop/message_pump_mac.mm

Issue 2709813003: [Mac] Reduce timer CPU use in MessagePumpCFRunLoopBase. (Closed)
Patch Set: Fix iOS compile problems. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/message_loop/message_pump_mac.h ('k') | base/message_loop/message_pump_mac_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/message_loop/message_pump_mac.mm
diff --git a/base/message_loop/message_pump_mac.mm b/base/message_loop/message_pump_mac.mm
index d924ead7cbd9b934341ab6314d059fe769c172fa..82d9f1e818b18c07af4b3ec2ac5e785de43af61d 100644
--- a/base/message_loop/message_pump_mac.mm
+++ b/base/message_loop/message_pump_mac.mm
@@ -69,8 +69,49 @@ const CFTimeInterval kCFTimeIntervalMax =
// Set to true if MessagePumpMac::Create() is called before NSApp is
// initialized. Only accessed from the main thread.
bool g_not_using_cr_app = false;
+
+// Various CoreFoundation definitions.
+typedef struct __CFRuntimeBase {
+ uintptr_t _cfisa;
+ uint8_t _cfinfo[4];
+#if __LP64__
+ uint32_t _rc;
+#endif
+} CFRuntimeBase;
+
+#if defined(__BIG_ENDIAN__)
+#define __CF_BIG_ENDIAN__ 1
+#define __CF_LITTLE_ENDIAN__ 0
#endif
+#if defined(__LITTLE_ENDIAN__)
+#define __CF_LITTLE_ENDIAN__ 1
+#define __CF_BIG_ENDIAN__ 0
+#endif
+
+#define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__)*3)
+
+#define __CFBitfieldMask(N1, N2) \
+ ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
+#define __CFBitfieldSetValue(V, N1, N2, X) \
+ ((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | \
+ (((X) << (N2)) & __CFBitfieldMask(N1, N2)))
+
+// Marking timers as invalid at the right time by flipping their valid bit helps
+// significantly reduce power use (see the explanation in
+// RunDelayedWorkTimer()), however there is no public API for doing so.
+// CFRuntime.h states that CFRuntimeBase can change from release to release
+// and should not be accessed directly. The last known change of this struct
+// occurred in 2008 in CF-476 / 10.5; unfortunately the source for 10.11 and
+// 10.12 is not available for inspection at this time.
+// CanInvalidateCFRunLoopTimers() will at least prevent us from invalidating
+// timers if this function starts flipping the wrong bit on a future OS release.
+void __ChromeCFRunLoopTimerSetValid(CFRunLoopTimerRef timer, bool valid) {
+ __CFBitfieldSetValue(((CFRuntimeBase*)timer)->_cfinfo[CF_INFO_BITS], 3, 3,
+ valid);
+}
+#endif // !defined(OS_IOS)
+
} // namespace
// static
@@ -94,6 +135,47 @@ class MessagePumpScopedAutoreleasePool {
DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool);
};
+#if !defined(OS_IOS)
+// This function uses private API to modify a test timer's valid state and
+// uses public API to confirm that the private API changed the correct bit.
+// static
+bool MessagePumpCFRunLoopBase::CanInvalidateCFRunLoopTimers() {
+ CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
+ timer_context.info = nullptr;
+ ScopedCFTypeRef<CFRunLoopTimerRef> test_timer(
+ CFRunLoopTimerCreate(NULL, // allocator
+ kCFTimeIntervalMax, // fire time
+ kCFTimeIntervalMax, // interval
+ 0, // flags
+ 0, // priority
+ nullptr, &timer_context));
+ // Should be valid from the start.
+ if (!CFRunLoopTimerIsValid(test_timer)) {
+ return false;
+ }
+ // Confirm that the private API can mark the timer invalid.
+ __ChromeCFRunLoopTimerSetValid(test_timer, false);
+ if (CFRunLoopTimerIsValid(test_timer)) {
+ return false;
+ }
+ // Confirm that the private API can mark the timer valid.
+ __ChromeCFRunLoopTimerSetValid(test_timer, true);
+ return CFRunLoopTimerIsValid(test_timer);
+}
+#endif // !defined(OS_IOS)
+
+// static
+void MessagePumpCFRunLoopBase::ChromeCFRunLoopTimerSetValid(
+ CFRunLoopTimerRef timer,
+ bool valid) {
+#if !defined(OS_IOS)
+ static bool can_invalidate_timers = CanInvalidateCFRunLoopTimers();
+ if (can_invalidate_timers) {
+ __ChromeCFRunLoopTimerSetValid(timer, valid);
+ }
+#endif // !defined(OS_IOS)
+}
+
// Must be called on the run loop thread.
MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
: delegate_(NULL),
@@ -243,6 +325,17 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork(
const TimeTicks& delayed_work_time) {
TimeDelta delta = delayed_work_time - TimeTicks::Now();
delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF();
+
+ // Flip the timer's validation bit just before setting the new fire time. Do
+ // this now because CFRunLoopTimerSetNextFireDate() likely checks the validity
+ // of a timer before proceeding to set its fire date. Making the timer valid
+ // now won't have any side effects (such as a premature firing of the timer)
+ // because we're only flipping a bit.
+ //
+ // Please see the comment in RunDelayedWorkTimer() for more info on the whys
+ // of invalidation.
+ ChromeCFRunLoopTimerSetValid(delayed_work_timer_, true);
+
CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_);
if (timer_slack_ == TIMER_SLACK_MAXIMUM) {
CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5);
@@ -264,6 +357,31 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
// The timer won't fire again until it's reset.
self->delayed_work_fire_time_ = kCFTimeIntervalMax;
+ // The message pump's timer needs to fire at changing and unpredictable
+ // intervals. Creating a new timer for each firing time is very expensive, so
+ // the message pump instead uses a repeating timer with a very large repeat
+ // rate. After each firing of the timer, the run loop sets the timer's next
+ // firing time to the distant future, essentially pausing the timer until the
+ // pump sets the next firing time. This is the solution recommended by Apple.
+ //
+ // It turns out, however, that scheduling timers is also quite expensive, and
+ // that every one of the message pump's timer firings incurs two
+ // reschedulings. The first rescheduling occurs in ScheduleDelayedWork(),
+ // which sets the desired next firing time. The second comes after exiting
+ // this method (the timer's callback method), when the run loop sets the
+ // timer's next firing time to far in the future.
+ //
+ // The code in __CFRunLoopDoTimer() inside CFRunLoop.c calls the timer's
+ // callback, confirms that the timer is valid, and then sets its future
+ // firing time based on its repeat frequency. Flipping the valid bit here
+ // causes the __CFRunLoopDoTimer() to skip setting the future firing time.
+ // Note that there's public API to invalidate a timer but it goes beyond
+ // flipping the valid bit, making the timer unusable in the future.
+ //
+ // ScheduleDelayedWork() flips the valid bit back just before setting the
+ // timer's new firing time.
+ ChromeCFRunLoopTimerSetValid(self->delayed_work_timer_, false);
+
// CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources.
// In order to establish the proper priority in which work and delayed work
// are processed one for one, the timer used to schedule delayed work must
« no previous file with comments | « base/message_loop/message_pump_mac.h ('k') | base/message_loop/message_pump_mac_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698