Energy Best Practices

Session 712 WWDC 2013

Your apps play a vital role in maximizing battery life. Learn how to use Power Tools and new APIs to write energy-efficient code. Find out if your app is using excessive CPU or timers, and how to adopt design patterns that will avoid these problems.

[ Silence ]

Good morning.

My name is Matt Jacobson.

I'm on the OS X Performance Team at Apple, and I want to talk to you today about Energy and Battery Life.

You've probably heard a lot about battery life this week at WWDC.

Battery life is really important to us.

And hopefully by now we've convinced you that not only do you want your apps to work great and look great and for your users to love how they work and feel, but you want them to be able to use them and still get great battery life.

And here's something you probably don't want.

Users using their machine on battery, as I see a lot of you out there are right now.

If it's the middle of the week, they see "Oh, the battery is low and I'll open up the battery status menu and see what's going on and there's some app that's hogging all the energy."

Well, you really don't want that to be your app.

So, I want to talk to you about some simple ways that you can make sure your app is as energy efficient as possible so you can avoid being the energy hogger on the system.

I want to talk about some new tools and features in Mavericks and Xcode 5 to help recognize energy issues.

I want to talk about how to diagnose those issues once you've found them.

I want to talk about a few common mistakes that you can make that make your app less energy efficient, and how to avoid them.

And I want to talk about how to adopt some best practices and new APIs for energy efficiency.

But first, let's start with some background.

What are the things that your app can do to use energy?

Well there are mainly three, running on the CPU, doing I/O to the network or a local disk, or using the graphic system.

Now, the distribution of how much power each of these uses varies from system to system.

But in all cases, the CPU can dominate.

So let's talk about the CPU.

Now, you might think that with our CPUs and our modern Macs with really high clock speeds and lots of cores and multiple threads per core that CPU time is basically unlimited, and in a lot of ways that's true.

But CPU time is still limited in one major way, and that's by the size of the battery.

And this is important for two reasons.

One, the OS is designed to give you basically as much CPU time as you ask for.

And a single bad app, a single app that is using too much CPU, it is not energy efficient, can totally ruin the user's battery life and here's why.

The CPUs in our Macs, especially the new Haswell ULT CPUs in our new MacBook Airs use a lot more power when they're running compared to when they're idle.

We're really good about aggressively power managing these CPUs.

But when they have to run code, they run at a much higher power state.

For example, if your app is using 1 percent CPU, the power draw from the CPU increases by 10 percent.

That's just for 1 percent.

If you're using 10 percent, the power draw from the CPU doubles.

And if you have a thread that's going full bore using 100 percent CPU time, the CPU can draw 10 times as much power compared to idle or more.

So, how do you use the CPU responsibly in your app?

Well, the main thing is this.

You should strive for your app to be absolutely idle when it's not in use.

So, that means eliminating any work that's not driven by the user.

So work done in response to timers, work done in response to chatting network, work done in response to inter-process communication of any kind.

It means eliminating things like persistent animations.

These things are relatively cheap but over the long term it adds up to a lot of energy.

And then when the user requests action, you want to be as efficient as possible.

And the best way to do this is to remember that fast code is usually efficient code.

So go back, look at your algorithms, make sure that they are as efficient as possible.

If it makes sense to do so, parallelize your work, use Grand Central Dispatch, harness the power of multicore, and then race back to idle.

And the reason this is important is that the faster you guys can get your apps idle, the faster we can get the system idle and the less energy we use overall.

If you want to know how much CPU time your app is using, just look at the energy tab in the Activity Monitor in Mavericks.

Just look at the percent CPU column.

Again, you guys should strive for this number to be absolutely zero when your app is not in use.

So 0.0. Even a small value like 2.7 percent can cause a significant power draw increase from the CPU.

And so it's worth investigating.

If you're debugging in Xcode you can go to the debug navigator and look at the CPU gauge.

There's the debug navigator, the CPU gauge, and it will tell you how much CPU time you're using instantaneously as well as a history of how much you've used over time.

And if you are the kind of person who's more familiar with the Unix shell, you can run the top command of course, look at the percent CPU column.

Or if you pass -A, put top into accumulator mode, what this does is it allows you to watch the CPU time of your app accumulate starting at 0, so you can see here I launched top and Stock Watcher has used 0.2 seconds of CPU time since I launched top.

So this allows you to get an idea of how much CPU time a particular scenario takes or just to watch your app accumulate CPU time in real time.

[ Pause ]

Now, if you see your app using CPU, you probably want to know why.

And this is where Instruments Time Profiler comes in handy.

A lot of you are probably familiar with Instruments having used it hopefully to diagnose performance problems.

If you haven't used Time Profiler, what it does is it takes a really high frequency sample of your app.

So it's really quickly seeing exactly where it's executing all the different call stacks, and then it aggregates all the call stacks into a Call Tree view that will show you what percent of the time is spent where, under which functions, which libraries, which frameworks, et cetera.

I'll show you a little more on how to use Instruments a little later.

One thing that's new in Mavericks is that the system will also automatically monitor your CPU usage.

So you may see a line in the console that looks like this.

You've got some app caught burning CPU.

It's used a lot of CPU over this much time.

And when this happens, the system will automatically drop a log into /Library/Logs/DiagnosticReports with the CPU resource.spin extension.

And what this log contains is a historical sample of your app, where it's been using CPU time.

And this is automatically done for you without having to set up anything.

And so this is basically the same thing that Instrument is getting, just in text form.

Now, it's important to remember that the threshold for triggering these is relatively high.

Here it says 50 percent CPU.

So, you should really treat these as a real red flag when you see them, if you see them, because they can cause a significant battery life decrease for your users.

So go through and make sure that these reports represent work that you know the user is aware of and ask for.

Another way to diagnose CPU time is to look for what I call secondary indicators.

So what do I mean by this?

Well, we talked about the three ways that apps can use the or use energy.

We talked about CPU.

But of course using the I/O system, using the graphic system also involve using the CPU to do that.

So looking at where you use the I/O system or the graphic system can give you a good pointer as to where you're using CPU time.

So let's look at I/O.

This little tool called fs usage that has shipped on OS X forever, and what it does is it tells you all the places where your app is interacting with the file system and network layers of the Kernel.

So these are things like, you know, open/closed system calls, read/writes from the network, disk I/Os, things like that.

So let's run it on an app.

Here you can see my app is making a lot of calls to stat for a file called myicon.png.

It's also actually writing to a file called downloads.plist.

So, there's no magic bullet here, but this can often give you a pretty good sense of where you're using CPU, at least what kind of processing you're doing.

If you're interested in fs usage or performance in general, there was a great session earlier this week, Building Efficient OS X Apps that talks a lot more about fs usage, talks a lot more about being a good system citizen with respect to system resources.

So I highly recommend you go back, give that a watch when you get a chance.

OK, so that was I/O.

Let's talk about graphics.

So for graphics we have a little app called QuartzDebug, and this ships in our developer toolkit.

And if you launch this app and check the little Flash screen updates box, what this will do is it will cause all draws to the screen to be flashed in yellow.

So I check this box, put up the OS X shutdown panel, and the Shutdown button was covered in yellow.

And this makes sense because the button is pulsing.

I expect it to be drawing.

But if you have a UI that looks pretty static, you might be surprised to see that, in some cases, it's actually drawing.

And of course drawing uses energy just because it's using the graphic system, it uses energy because it's using the CPU.

And in a lot of cases, this drawing could be invisible or almost invisible to the naked eye.

So what QuartzDebug does is allows you to see it a lot more clearly.

QuartzDebug is available in our Graphics Tools for Xcode package on developer.apple.com.

So, CPU usage.

Remember that a little bit of CPU usage, even like 1 percent, causes a significant power draw increase from the CPU.

So, it's important to make sure your app is absolutely idle when it's not in use.

Go through, watch it with Activity Monitor, make sure it's idle when it should be.

All right, let's talk about timers.

I'm sure you use timers in your app in a lot of places.

Timers are used to schedule periodic events or future events and drive animations, you use timers.

So why are timers interesting from an energy perspective?

Well, timers keep your app out of idle.

When you have a timer and it wakes up and it runs some code, that's keeping your app out of idle.

That's bringing the system out of idle.

And perhaps more importantly, timers can have an outsize effect on energy.

So what do I mean by this?

Imagine you have an app, and it's got some repeating timer, here are all the places where it's firing.

Now, every time one of these timers fires, there's a little bit of overhead involved in bringing the CPU out of idle, bringing your app out of idle, getting your app context switched to, getting your app back to userland.

So, there's a little extra wake up cost associated with that.

But it gets worse because we need more than just the CPU to run your timer.

We need all the supporting chipset.

So we need the memory controller, we need the bus controller and all that.

And we really try and aggressively power manage these chips.

But they take a little longer to go back into idle and to come out of idle.

So if you have really frequent timers like this, the system might decide, "Well, I don't really have time to put those chips back into idle.

I'm just going to leave them on."

So you incur all of this energy overhead, all this red, even if this timer is just doing a little bit of work.

By the way, what do I mean by timers?

Of course we have explicit timer APIs in Grand Central Dispatch, Core Foundation foundation.

There are low level APIs like sleep and usleep that are implemented with timers, also things like pthread cond timedwait if you're using pthreads or dispatch semaphore wait, if you're using libdispatch.

Even a lot of low level APIs that take timeouts like select and poll, even MockMessage takes a timeout.

So these can all, if those timeouts fire, cause timers.

And there are a lot of high level APIs that are built on timers.

So if you use dispatch after or NSObject performSelector: withObject:afterDelay.

Even things like an NSProgressIndicator is implemented with timers to advance the little spinny wheel or the barber pole.

Or if you use CVDisplayLink, that is a timer as well.

In Mavericks, it's a lot easier to see the timer usage of your app if you go back to Activity Monitor and look at the Idle Wakeups column.

This will show you the number of times a timer fired in your app in the last second.

So in this case, this app fired 10 timers in the last second.

There's also a new column called Energy Impact, and this is a measure of the overall energy impact of your app.

So that includes CPU usage, timer wakeups and all the overhead associated with that, and some other factors too.

So this is a good way to get a quick view of whether you're using energy on the system.

If you're using zero, that's great.

If you're using nonzero, well, you want to know why.

You can get the same stuff right in Xcode and Xcode 5.

Go back to the Debug Navigator and look at the Energy Impact Gauge.

This will give you the number of wakes over the last second and a readout of your energy utilization.

So once you see that you're incurring a lot of wakeups, you probably want to know why.

And there's a new tool in Mavericks called Timer Fires that will help you figure this out.

If you just run it, give it the process ID you're interested in, and -s to enable stack traces.

What this will do is it will log all the places where your app has woken up due to a timer.

So let's look at this.

Here it looks like my app has a 2 hertz timer.

That's a CF timer that's executing routine in its app delegate called timer fired.

And here it looks like we've got a libdispatch timer, a Grand Central Dispatch timer that is executing a routine called updateWidgets.

And here's where the app actually just call usleep.

So you can see the full stack trace of where it called usleep.

All right, so let's talk about some ways that you don't want to be using timers.

Here's a pretty naive example, but it's one that we have seen.

Say you've got a thread that's doing some long-running work like waiting on the network.

And once it's done it's going to set a global Boolean to say "I'm done."

And then you've got a second thread that wants to know when that work is done and then run a completion block or a completion handler.

And the way it's going to do that right now is it's going to say, "OK, I'll check 10 times a second to see whether the work is done," and a little loop there.

"And then once it's done, I'll just run the completion block."

Well, first of all, this doesn't work for several reasons.

But the reason it's bad for energy is that most of the time, this timer is going to wake up.

It's going to check whether the work is done, and the work is not going to be done.

And so it's going to go back to sleep.

Come back up and say, "Is the work done?

No. Is the work done?

No."

So, every time you wake up, you're incurring all of that red energy we saw before.

So this is no good.

Instead, in this case, in this kind of case you want to use an explicit synchronization method.

For most of you the best option is Grand Central Dispatch, so you can use a serial dispatch queue or a semaphore.

If you're using pthreads there are condition variables that will do this for you.

So in this kind of case what we'll do is I'll put that long-running work on to a serial queue, and then I'll make sure I put the completion block on to the queue afterwards.

And then the completion block will just automatically run when the work is done, no need to wake up and check.

Grand Central Dispatch will just handle it for me.

Here's another example.

Here we've got a 2 hertz timer, an NSTimer, that is trying to find out whether a file has changed.

So it's waking up saying "Did this file change?

No. Did this file change?

No." And the common case is that the file is not going to change.

The user is not changing their files very much.

So this is really inefficient obviously because we're incurring all that wakeup overhear every time we have to wake up.

In this kind of case, you want to consider using an explicit event stream.

And there are tons of them on OS X and iOS.

In this kind of case, I'm interested in the File System Event so I can use something like DISPATCH SOURCE TYPE VNODE for a small scale particular file update.

If you're interested in multiple directories you can use the FSEvents framework for file system-wide or directory-wide events.

If you are interested in events from other processes, of course there's XPC.

Or if you want to use a distributed notification, there are distributed notifications at the foundation level and its distributed notification center, as well as BSD notifications.

If you're interested in device notifications, there are a lot of different ways to do that.

For example if you are interested in when a mass storage volume is attached or removed, there are disk arbitration notifications.

I/O Kit publishes notifications on when certain other types of devices are attached.

And if you're interested in events from the network, of course there's Apple Push Notifications, there's also Bonjour, and network reachability notification, so you don't have to wake up and say, "Can I reach the server?

No. Can I reach the server?

No." So in this case, I will replace the timer with a dispatch source.

I'll give it DISPATCH SOURCE TYPE VNODE and the file descriptor I'm interested in.

And then when the event fires, I'll just say, "OK, go ahead and check for the file.

The system told me that the file changed.

I'll go ahead and run my handler."

OK, here's another common case.

This is a basic run loop type construct where we're waiting for an event, and we're doing that by waiting on a semaphore in this case, and then we're when we get an event we'll see if we have work to do.

The problem here is I've specified a timeout to dispatch semaphore wait, but I'm not really doing anything special if dispatch semaphore wait times out.

So this is a common mistake because dispatch semaphore wait, and a lot of APIs like it, take a timeout parameter.

So a lot of people say, "Well, I better specify a timeout anyway."

Actually in most cases you can specify a special argument that says "No, just go ahead and block forever.

Don't make this into a timer at all.

Just block until I get an event, or indefinitely otherwise."

And if you do specify a timeout, make sure it represents a meaningful state change to you, something that you're going to check for like an error condition or a timeout, you know, like a network timeout.

Here's another case.

You probably know a lot about memory leaks.

But you're going to have energy leaks too.

So this is a case where I've set up a recurring timer to do some work.

And it's doing some valuable work for me, but then I don't need it anymore, but I don't invalidate it.

And so what's going to happen is for the rest of the lifetime of my app, I'm going to have this timer that's waking up, probably doing no work, using lots of energy, hurting the user's battery life.

This is no good.

So go through, make sure that all the repeating timers you have are invalidated or cancelled correctly when you no longer need them.

Finally, you really want to specify timer tolerance.

You may have heard a little bit about tolerance this week.

Tolerance is a hint to the system as to how much it can batch work together.

And this is good because the more we batch work together, the less wakeup overhead we have, and the more the system is idle in its low power state.

So it's really easy to specify a tolerance for all your timers if you have dispatch timers.

It's just the final parameter to dispatch source set timer.

This is in nanoseconds.

If you have CFRunLoop timers, there is new API in Mavericks, CFRunLoopTimerSetTolerance, and that takes a value in seconds.

And similarly for NSTimers.

OK, I want to show you a quick demo now on how this all works together.

So, I'm running Mavericks here and Xcode 5, and I've got this little app called Stock Watcher that I'm working on.

And it's just a little stock ticker app.

It shows me the price and change of all the stocks I'm interested in.

And look, it's even a good energy citizen because it suspends its animations when it's not in the foreground.

But I'm really interested in the energy efficiency of my app.

So let me go to the Debug Navigator and I'll look at the CPU gauge first.

OK, so my app appears to be idle, but it says it's using 2 to 3 percent CPU time.

What I really want is for my app to be absolutely idle when it's not doing any work for me.

So let me just click Profile in Instruments.

This will take me right into Instruments and start running a time profiler.

So, again, what Instruments is doing is it's taking a really high frequency sample of my app, seeing exactly the stacks where it's executing, and then it will show me those in the call to review.

So I'll stop it now.

And if I just option click on this disclosure triangle, I can see the full call tree hierarchy of where it's executing.

And in this case it looks like, OK, it's doing a lot of drawing work.

OK. I can also open up the Extended Detail View, this will also show me the heaviest stack during the sample period.

OK, it's doing a lot of views, view drawing, control drawing.

OK, so I know this has something to do with drawing.

So let me try a different method.

I'll close out Instruments.

If I run this again, I'll turn on show view drawing here in Xcode.

And this is basically the same thing as QuartzDebug like we talked about before.

And what this does is it cause all views, right when they draw rect to show yellow right beforehand.

And we can see almost immediately, it's this top S&P 500 view that's blinking out of control.

OK, so that gives us a pretty good idea.

Let me show you one more thing.

So I'll go into terminal and I'll run fs usage on Stock Watcher.

OK, so immediately we see lots of output.

What's happening here it looks like is my app is calling a few system calls on this stocks.plist file.

So list xsetter, get xsetter, lstat.

OK, so I happen to know what this is.

This is a beta version of my app.

It's not getting data from the network, it's getting data from a local plist.

So let me stop Stock Watcher and go to there.

So I know this is in a routine called check for stock.plist update.

OK. I actually did some debugging in here before, and I noticed that even if the file I'm interested in didn't change, I'm still updating the S&P 500 view.

So let me just move this into my IF statement here.

And I'll build and run.

OK. So let's go back into the CPU column.

Great. The CPU Gauge says I'm using 0 percent CPU.

We can even turn on Show View Drawing.

OK, awesome.

No View Drawing.

Great. But let's look at the Energy Impact Gauge.

So my app is idle.

I really want my energy impact my energy utilization to be 0.

But right now it's low and it also says my app is waking up 10 times a second.

All right, so what I'll do is let's run Timer Fires on it.

OK, so Timer Fires is telling me that that same routine, check for stock.plist update is firing and it looks like it ran 10 times a second.

So let's go back.

Check for stock.plist update.

OK. So this is actually called on a timer up here.

So this is a timer and this is checking to see whether the plist file changes.

But it's firing 10 times a second, and of course, we're not changing the plist file.

So this is a waste of energy.

So I'm going to rip this timer out and I'm going to replace it with a dispatch source.

[ Pause ]

OK. So what this is saying is I'll get a file descriptor for the plist.

I'll set up a Dispatch Source type of VNODE, say "Fire this on my main queue."

And when it does fire, I'll just run check for stock.plist update.

Great. So let's build and run.

[ Pause ]

And if go back to the Energy Impact Gauge, you can see my energy utilization is 0 most of the time.

I still have a few other things to debug, it looks like.

So but let me show you one more thing.

So it's really important to not just test your app when it launches but also test all the paths your app can go down because your user is going to hit those paths.

So I actually recently noticed that, you know, you saw that Stock Watcher turns off its animation when it's in the background.

If I switch away from Stock Watcher during an animation, the energy utilization remains at high, and Xcode is telling me it has 90 wakes a second.

So this is really bad.

OK, I'll go back and do the [inaudible] Timer Fires again.

OK, so it looks like the timer that I'm interested in is firing a routine called SWT stock table view animator animation step.

So let me go back in Xcode.

OK, so this is the timer that is actually moving all the stock quotes when they animate, and when it's over, it's supposed to end the animation.

Oh, OK. So what's happening here is if the animation is still running then excuse me, if the animation is not running, then we don't end up invalidating that timer.

So let me just move this out.

[ Pause ]

I'll build and run.

All right, let's try again.

I'll switch away right in the middle of one of those animations.

Let's look at the Energy Impact Gauge.

All right.

So, for the most part, my energy impact is 0.

Great. This is exactly what I want.

My app is absolutely idle when it's not in use.

OK, let's go back to the slides.

So timers.

Remember that timers involve a lot of wakeup overhead.

So you want to make sure that you don't have a high rate of idle wakeups especially when your app is idle, not doing any useful works for the user, not doing anything that the user expects.

And if it is, use our new tools and features in Xcode 5 and Mavericks to help debug it.

All right.

I want to talk about one more things, and that's background work.

So we talked a lot about making sure your app is idle, no CPU usage, no wakeups when it's not in use.

But you probably have some periodic work that you know you have to do every so often.

And I want to show you how you can do it in an energy-efficient way.

So, one example of this is periodic animations.

We saw that Stock Watcher is stopping its animations when it goes into the background.

Animations are can be relatively inexpensive, you know, optimized drawing routines, low frame rate.

But remember that even a small amount of CPU usage, like an animation, can cause a significant power draw increase.

So and in a lot of cases, these animations might not even be visible to the user.

So what Stock Watcher was doing was disabling its animation in the background.

It's just doing that by implementing the ApplicationDidResignActive, and the ApplicationDidBecomeActive methods in its app delegate.

Or if you just need to check at runtime, you can ask NSApp is active.

But in some cases, you might actually only want to disable certain animations when the window you're looking at is occluded.

So something like that.

In that case, there's new API in Mavericks to notify you when an application or window has become occluded.

If you're interested in the application, just implement the applicationDid ChangeOcclusionState routine in your app delegate.

For Windows, just check or implement rather WindowDidChangeOcclusionState in your window delegate.

If you want to see a great example of this, there's talk earlier this week about App Nap called Improving Power Efficiency with App Nap, that was yesterday.

Go back and watch that in the WWDC app.

There's a great example of how you can use these to be a lot more power-efficient.

Another way you can improve the energy efficiency of your background work is to batch it together.

So if you have background work that's really spread out over time, this decreases the amount of time that we can get the machine idle.

So batching it all together improves the idleness of the machine, reduces wakeup costs, and allows us to be overall more energy-efficient.

So here's an example of batching.

Say I have an image cache in my app and I want to make sure there weren't stale images in it.

So every time I add an image to it, I also set a little timer saying "OK, in 5 seconds, run this routine to make sure this image is not stale anymore, or is not stale.

And if it is then remove it."

And then in that routine I say, "OK, is the image stale?

If so, remove it.

If not, check again in another 5 seconds."

Well, this is kind of bad if I just have one image in my cache then I'm going to be waking up every 5 seconds, making sure that the image isn't stale.

If I have lots of images in my cache, I'm going to be waking up all the time at all sorts of different time saying, "Is this image stale?

Is this image stale?"

So this work can be batched together.

So I could change this code to say, "OK, when I cache an image, just add it to the list of images."

And then when I know I'm going to have to do some other work like handling a user event, I can just go ahead and kick off this cache checking work to another dispatch queue.

That way, it doesn't interfere with the user interaction.

Go through each of them.

Prune all the stale ones.

[ Pause ]

And you may have some periodic work that is even lower priority that can wait even longer.

And for that, we have a new API in Mavericks called Centralized Task Scheduling.

And what this does is it allows you to tell the system, "Here, I've got this bit of work that is really low priority, and it's actually it's a low priority that I'm OK waiting for the user to plug their machine back in before I start it."

So this is stuff like cleaning up temporary files, maybe sinking or indexing, checking for new updates is a common one.

So new API in XPC that allows you to do it, and I'll show you how to use it now.

So first, you just create a new empty XPC dictionary.

You say, "OK, this is a one-time event," so you're telling XPC, "This doesn't have to repeat.

It's just once."

I want this to happen in an hour, 3,600 seconds, but I'm OK if it doesn't start for another 6 hours after that, or even longer, maybe even 24 hours.

So and then I tell XPC that the priority of this activity is maintenance.

And what this means is "Hey, XPC, try to do this when the user has plugged back in their machine, and also when they're not using their machine so it doesn't get in the way of responsiveness."

Then you just go ahead, register your activity, pass that dictionary in, give it a label, and give it a block with the work you want to do.

XPC will take care of the rest.

It'll run your block on its own queue.

It'll, you know, you'll have the work done for you at an appropriate time, and you won't interfere with the user's battery life.

There's a lot more to XPC activity, and XPC in general, there's a session earlier this week, Efficient Design with XPC.

If you're interested, go ahead and give that a watch online.

So if you only remember one thing from today, let it be this, that a small amount of CPU usage causes a large power increase from the CPU.

So you want your apps to achieve absolute idle when they're not in use.

Now, I know you guys can take this advice and go back and make your apps as energy-efficient as possible, and I can't wait for that, but I also want you guys to integrate this into your release process.

So test for energy efficiency just like you would test for performance or reliability or correctness.

If you have any questions about energy efficiency, you can contact our Core S Technologies Evangelist, that's Paul Denbaum.

If you have questions about Instruments, you can contact Dave DeLong or look at the Instruments User Guide on developer.apple.com.

And of course, you can ask questions on the Apple developer forums.

We've had a ton of great sessions this week about battery life and energy, so definitely go back if you're interested and watch these, especially, you know, Maximizing Battery Life on OS X will give you a great sort of overview of everything we've done and everything we'd like you to do for energy efficiency and battery life.

If you're interested in App Nap and most of you should be, look at the Improving Power Efficiency with App Nap session.

If you write for the web, there's a great session yesterday about how you can make an energy efficient website.

Of course there was the XPC session, and I highly recommend the Building Efficient OS X session which was on Tuesday if you are interested in just being a good system citizen in general with respect to energy performance and everything.

Thank you so much for coming.

Enjoy the rest of the week.

Enjoy the bash.

Happy WWDC.

[Applause]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US