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

Unified Diff: base/message_loop/message_pump_mac.mm

Issue 2709813003: [Mac] Reduce timer CPU use in MessagePumpCFRunLoopBase. (Closed)
Patch Set: 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
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..5556be3f6cd4ff94deb8034493c6e4e7f3a2bf80 100644
--- a/base/message_loop/message_pump_mac.mm
+++ b/base/message_loop/message_pump_mac.mm
@@ -69,6 +69,74 @@ 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)))
+
+void ChromeCFSetValid(void* cf) {
+ __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1);
+}
+
+void ChromeCFUnsetValid(void* cf) {
Mark Mentovai 2017/02/21 17:41:22 Might be better as a single function that takes a
shrike 2017/02/22 00:57:52 Acknowledged.
+ __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0);
+}
+
+// Marking timers as invalid at the right time helps significantly reduce power
+// use (see the explanation in RunDelayedWorkTimer()), however there is no
+// public API for doing so. CFRuntime.h states that CFRuntimeBase, upon which
+// the above timer invalidation functions are based, can change from release to
+// release and should not be accessed directly (this struct last changed in
+// 2008 in CF-476).
Mark Mentovai 2017/02/21 18:10:57 Call out 10.5 by name too. But this is kind of fu
shrike 2017/02/22 00:57:52 I added a unit test to make sure there's no side e
Mark Mentovai 2017/02/23 02:34:36 I’m mean that we’re flipping an internal bit that
shrike 2017/02/23 17:51:11 OK, I agree that there is a small chance of what y
+//
+// 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 right bit.
+bool CanInvalidateTimers() {
+ CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
+ timer_context.info = NULL;
Mark Mentovai 2017/02/21 17:41:22 Use nullptr in new Chrome code whenever you have N
shrike 2017/02/22 00:57:52 Thank you. Fixed.
+ ScopedCFTypeRef<CFRunLoopTimerRef> test_timer(
+ CFRunLoopTimerCreate(NULL, // allocator
+ kCFTimeIntervalMax, // fire time
+ kCFTimeIntervalMax, // interval
+ 0, // flags
+ 0, // priority
+ NULL, &timer_context));
+ // Should be valid from the start.
+ if (!CFRunLoopTimerIsValid(test_timer)) {
+ return false;
+ }
+ // Confirm that the private API can mark the timer invalid.
+ ChromeCFUnsetValid(test_timer);
+ if (CFRunLoopTimerIsValid(test_timer)) {
+ return false;
+ }
+ // Confirm that the private API can mark the timer valid.
+ ChromeCFSetValid(test_timer);
+ return CFRunLoopTimerIsValid(test_timer);
+}
#endif
} // namespace
@@ -99,11 +167,16 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
: delegate_(NULL),
delayed_work_fire_time_(kCFTimeIntervalMax),
timer_slack_(base::TIMER_SLACK_NONE),
+ can_invalidate_timers_(false),
Mark Mentovai 2017/02/21 17:41:22 Might as well wrap this and the member variable in
shrike 2017/02/22 00:57:52 This has been removed, per your feedback below.
nesting_level_(0),
run_nesting_level_(0),
deepest_nesting_level_(0),
delegateless_work_(false),
delegateless_idle_work_(false) {
+#if !defined(OS_IOS)
+ can_invalidate_timers_ = CanInvalidateTimers();
+#endif // !defined(OS_IOS)
+
run_loop_ = CFRunLoopGetCurrent();
CFRetain(run_loop_);
@@ -243,6 +316,12 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork(
const TimeTicks& delayed_work_time) {
TimeDelta delta = delayed_work_time - TimeTicks::Now();
delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF();
+#if !defined(OS_IOS)
+ // Please see the comment in RunDelayedWorkTimer() for more info.
+ if (can_invalidate_timers_) {
Mark Mentovai 2017/02/21 17:41:22 Actually, I think that we can make this unconditio
shrike 2017/02/22 00:57:52 Got it. That's clean :-).
+ ChromeCFSetValid(delayed_work_timer_);
Mark Mentovai 2017/02/21 17:41:22 Should this happen before (as done here) or after
shrike 2017/02/22 00:57:52 Done.
+ }
+#endif // !defined(OS_IOS)
CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_);
if (timer_slack_ == TIMER_SLACK_MAXIMUM) {
CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5);
@@ -264,6 +343,35 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
// The timer won't fire again until it's reset.
self->delayed_work_fire_time_ = kCFTimeIntervalMax;
+#if !defined(OS_IOS)
+ // 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 invaildate 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.
+ if (self->can_invalidate_timers_) {
+ ChromeCFUnsetValid(self->delayed_work_timer_);
+ }
+#endif // !defined(OS_IOS)
+
// 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

Powered by Google App Engine
This is Rietveld 408576698