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

Side by Side 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 unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "base/message_loop/message_pump_mac.h" 5 #import "base/message_loop/message_pump_mac.h"
6 6
7 #import <Foundation/Foundation.h> 7 #import <Foundation/Foundation.h>
8 8
9 #include <limits> 9 #include <limits>
10 10
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 void NoOp(void* info) { 62 void NoOp(void* info) {
63 } 63 }
64 64
65 const CFTimeInterval kCFTimeIntervalMax = 65 const CFTimeInterval kCFTimeIntervalMax =
66 std::numeric_limits<CFTimeInterval>::max(); 66 std::numeric_limits<CFTimeInterval>::max();
67 67
68 #if !defined(OS_IOS) 68 #if !defined(OS_IOS)
69 // Set to true if MessagePumpMac::Create() is called before NSApp is 69 // Set to true if MessagePumpMac::Create() is called before NSApp is
70 // initialized. Only accessed from the main thread. 70 // initialized. Only accessed from the main thread.
71 bool g_not_using_cr_app = false; 71 bool g_not_using_cr_app = false;
72
73 // Various CoreFoundation definitions.
74 typedef struct __CFRuntimeBase {
75 uintptr_t _cfisa;
76 uint8_t _cfinfo[4];
77 #if __LP64__
78 uint32_t _rc;
79 #endif
80 } CFRuntimeBase;
81
82 #if defined(__BIG_ENDIAN__)
83 #define __CF_BIG_ENDIAN__ 1
84 #define __CF_LITTLE_ENDIAN__ 0
85 #endif
86
87 #if defined(__LITTLE_ENDIAN__)
88 #define __CF_LITTLE_ENDIAN__ 1
89 #define __CF_BIG_ENDIAN__ 0
90 #endif
91
92 #define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__)*3)
93
94 #define __CFBitfieldMask(N1, N2) \
95 ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
96 #define __CFBitfieldSetValue(V, N1, N2, X) \
97 ((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | \
98 (((X) << (N2)) & __CFBitfieldMask(N1, N2)))
99
100 void ChromeCFSetValid(void* cf) {
101 __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1);
102 }
103
104 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.
105 __CFBitfieldSetValue(((CFRuntimeBase*)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0);
106 }
107
108 // Marking timers as invalid at the right time helps significantly reduce power
109 // use (see the explanation in RunDelayedWorkTimer()), however there is no
110 // public API for doing so. CFRuntime.h states that CFRuntimeBase, upon which
111 // the above timer invalidation functions are based, can change from release to
112 // release and should not be accessed directly (this struct last changed in
113 // 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
114 //
115 // This function uses private API to modify a test timer's valid state and
116 // uses public API to confirm that the private API changed the right bit.
117 bool CanInvalidateTimers() {
118 CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
119 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.
120 ScopedCFTypeRef<CFRunLoopTimerRef> test_timer(
121 CFRunLoopTimerCreate(NULL, // allocator
122 kCFTimeIntervalMax, // fire time
123 kCFTimeIntervalMax, // interval
124 0, // flags
125 0, // priority
126 NULL, &timer_context));
127 // Should be valid from the start.
128 if (!CFRunLoopTimerIsValid(test_timer)) {
129 return false;
130 }
131 // Confirm that the private API can mark the timer invalid.
132 ChromeCFUnsetValid(test_timer);
133 if (CFRunLoopTimerIsValid(test_timer)) {
134 return false;
135 }
136 // Confirm that the private API can mark the timer valid.
137 ChromeCFSetValid(test_timer);
138 return CFRunLoopTimerIsValid(test_timer);
139 }
72 #endif 140 #endif
73 141
74 } // namespace 142 } // namespace
75 143
76 // static 144 // static
77 const CFStringRef kMessageLoopExclusiveRunLoopMode = 145 const CFStringRef kMessageLoopExclusiveRunLoopMode =
78 CFSTR("kMessageLoopExclusiveRunLoopMode"); 146 CFSTR("kMessageLoopExclusiveRunLoopMode");
79 147
80 // A scoper for autorelease pools created from message pump run loops. 148 // A scoper for autorelease pools created from message pump run loops.
81 // Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare 149 // Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare
(...skipping 10 matching lines...) Expand all
92 private: 160 private:
93 NSAutoreleasePool* pool_; 161 NSAutoreleasePool* pool_;
94 DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); 162 DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool);
95 }; 163 };
96 164
97 // Must be called on the run loop thread. 165 // Must be called on the run loop thread.
98 MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() 166 MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
99 : delegate_(NULL), 167 : delegate_(NULL),
100 delayed_work_fire_time_(kCFTimeIntervalMax), 168 delayed_work_fire_time_(kCFTimeIntervalMax),
101 timer_slack_(base::TIMER_SLACK_NONE), 169 timer_slack_(base::TIMER_SLACK_NONE),
170 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.
102 nesting_level_(0), 171 nesting_level_(0),
103 run_nesting_level_(0), 172 run_nesting_level_(0),
104 deepest_nesting_level_(0), 173 deepest_nesting_level_(0),
105 delegateless_work_(false), 174 delegateless_work_(false),
106 delegateless_idle_work_(false) { 175 delegateless_idle_work_(false) {
176 #if !defined(OS_IOS)
177 can_invalidate_timers_ = CanInvalidateTimers();
178 #endif // !defined(OS_IOS)
179
107 run_loop_ = CFRunLoopGetCurrent(); 180 run_loop_ = CFRunLoopGetCurrent();
108 CFRetain(run_loop_); 181 CFRetain(run_loop_);
109 182
110 // Set a repeating timer with a preposterous firing time and interval. The 183 // Set a repeating timer with a preposterous firing time and interval. The
111 // timer will effectively never fire as-is. The firing time will be adjusted 184 // timer will effectively never fire as-is. The firing time will be adjusted
112 // as needed when ScheduleDelayedWork is called. 185 // as needed when ScheduleDelayedWork is called.
113 CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); 186 CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
114 timer_context.info = this; 187 timer_context.info = this;
115 delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator 188 delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator
116 kCFTimeIntervalMax, // fire time 189 kCFTimeIntervalMax, // fire time
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 void MessagePumpCFRunLoopBase::ScheduleWork() { 309 void MessagePumpCFRunLoopBase::ScheduleWork() {
237 CFRunLoopSourceSignal(work_source_); 310 CFRunLoopSourceSignal(work_source_);
238 CFRunLoopWakeUp(run_loop_); 311 CFRunLoopWakeUp(run_loop_);
239 } 312 }
240 313
241 // Must be called on the run loop thread. 314 // Must be called on the run loop thread.
242 void MessagePumpCFRunLoopBase::ScheduleDelayedWork( 315 void MessagePumpCFRunLoopBase::ScheduleDelayedWork(
243 const TimeTicks& delayed_work_time) { 316 const TimeTicks& delayed_work_time) {
244 TimeDelta delta = delayed_work_time - TimeTicks::Now(); 317 TimeDelta delta = delayed_work_time - TimeTicks::Now();
245 delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); 318 delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF();
319 #if !defined(OS_IOS)
320 // Please see the comment in RunDelayedWorkTimer() for more info.
321 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 :-).
322 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.
323 }
324 #endif // !defined(OS_IOS)
246 CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); 325 CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_);
247 if (timer_slack_ == TIMER_SLACK_MAXIMUM) { 326 if (timer_slack_ == TIMER_SLACK_MAXIMUM) {
248 CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5); 327 CFRunLoopTimerSetTolerance(delayed_work_timer_, delta.InSecondsF() * 0.5);
249 } else { 328 } else {
250 CFRunLoopTimerSetTolerance(delayed_work_timer_, 0); 329 CFRunLoopTimerSetTolerance(delayed_work_timer_, 0);
251 } 330 }
252 } 331 }
253 332
254 void MessagePumpCFRunLoopBase::SetTimerSlack(TimerSlack timer_slack) { 333 void MessagePumpCFRunLoopBase::SetTimerSlack(TimerSlack timer_slack) {
255 timer_slack_ = timer_slack; 334 timer_slack_ = timer_slack;
256 } 335 }
257 336
258 // Called from the run loop. 337 // Called from the run loop.
259 // static 338 // static
260 void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, 339 void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
261 void* info) { 340 void* info) {
262 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); 341 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
263 342
264 // The timer won't fire again until it's reset. 343 // The timer won't fire again until it's reset.
265 self->delayed_work_fire_time_ = kCFTimeIntervalMax; 344 self->delayed_work_fire_time_ = kCFTimeIntervalMax;
266 345
346 #if !defined(OS_IOS)
347 // The message pump's timer needs to fire at changing and unpredictable
348 // intervals. Creating a new timer for each firing time is very expensive, so
349 // the message pump instead uses a repeating timer with a very large repeat
350 // rate. After each firing of the timer, the run loop sets the timer's next
351 // firing time to the distant future, essentially pausing the timer until the
352 // pump sets the next firing time. This is the solution recommended by Apple.
353 //
354 // It turns out, however, that scheduling timers is also quite expensive, and
355 // that every one of the message pump's timer firings incurs two
356 // reschedulings. The first rescheduling occurs in ScheduleDelayedWork(),
357 // which sets the desired next firing time. The second comes after exiting
358 // this method (the timer's callback method), when the run loop sets the
359 // timer's next firing time to far in the future.
360 //
361 // The code in __CFRunLoopDoTimer() inside CFRunLoop.c calls the timer's
362 // callback, confirms that the timer is valid, and then sets its future
363 // firing time based on its repeat frequency. Flipping the valid bit here
364 // causes the __CFRunLoopDoTimer() to skip setting the future firing time.
365 // Note that there's public API to invaildate a timer but it goes beyond
366 // flipping the valid bit, making the timer unusable in the future.
367 //
368 // ScheduleDelayedWork() flips the valid bit back just before setting the
369 // timer's new firing time.
370 if (self->can_invalidate_timers_) {
371 ChromeCFUnsetValid(self->delayed_work_timer_);
372 }
373 #endif // !defined(OS_IOS)
374
267 // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. 375 // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources.
268 // In order to establish the proper priority in which work and delayed work 376 // In order to establish the proper priority in which work and delayed work
269 // are processed one for one, the timer used to schedule delayed work must 377 // are processed one for one, the timer used to schedule delayed work must
270 // signal a CFRunLoopSource used to dispatch both work and delayed work. 378 // signal a CFRunLoopSource used to dispatch both work and delayed work.
271 CFRunLoopSourceSignal(self->work_source_); 379 CFRunLoopSourceSignal(self->work_source_);
272 } 380 }
273 381
274 // Called from the run loop. 382 // Called from the run loop.
275 // static 383 // static
276 void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { 384 void MessagePumpCFRunLoopBase::RunWorkSource(void* info) {
(...skipping 479 matching lines...) Expand 10 before | Expand all | Expand 10 after
756 [NSApplication sharedApplication]; 864 [NSApplication sharedApplication];
757 g_not_using_cr_app = true; 865 g_not_using_cr_app = true;
758 return new MessagePumpNSApplication; 866 return new MessagePumpNSApplication;
759 #endif 867 #endif
760 } 868 }
761 869
762 return new MessagePumpNSRunLoop; 870 return new MessagePumpNSRunLoop;
763 } 871 }
764 872
765 } // namespace base 873 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698