WWDC2004 Session 434

Transcript

Kind: captions
Language: en
hello welcome to session for 34 Coco
performance techniques
name is Troy Stevens I'm a software
engineer and the cocoa frameworks group
at Apple and i'm here today to talk to
you about performance and optimization
specifically as they applied in
developing cocoa applications on mac OS
pen now as we all probably know
performance is work and diligence but it
can also be exciting and rewarding it
can pay off and applications that your
users want to use if there's one thing
that users love it's a well optimized
responsive application one that not only
empowers them to do something of
interest but is ready to respond to
their requests and provide feedback with
quick turnaround to interact with them
in a way that is responsive so
optimization looks we're living so
turnaround is part of it and one thing
that you want to do is figure out where
the opportunities for optimization in
your application lie and how do you do
that how do we figure out where to do
things well it's partly a matter of
metrics of using measurement techniques
and measurement tools it's partly a
matter of having some general ideas
about optimization that may apply you
may brought these from other platforms
that you've worked on and it's partly a
matter of knowing the framework that
you're working within the platform that
you're working with knowing the various
capabilities that it provides their
performance characteristics and so forth
so that's what I want to talk to you
about today the other the other issues
the platform independent issues about
performance are easily covered elsewhere
their comprehensively covered elsewhere
today I want to talk specifically about
Coco and in particular we'll look at
I'll try to point out to you some common
performance techniques some issues that
you may encounter commonly as you're
developing Coco applications things lots
of people run into I'll try to point out
some API usage techniques and
recommended usage patterns that you can
use to help improve
formance and we'll also look at
alternative ways different ways that
cocoa provides a variety of ways to
perform a different tent as a particular
task that give you the opportunity to
choose the way that is most conducive to
the getting the best performance so it's
prerequisites for this talk I assume
only that you have some general
experience developing in cocoa some
familiarity more or less with the
various api's and classes we
capabilities that it provides and if you
have some general knowledge of
performance techniques that you've
brought from another platform that could
be helpful too but it's not strictly
required so first before we dive into
the cocoa specifics I want to cover some
general concepts just to get us into the
right mindset for thinking about
optimization there's a lot of wisdom
that's been accumulated from various
other platforms that we all have worked
on some things about performance are
constants and we can learn from that so
one of the most important things about
performance tuning is it involves
trade-off some give and take on one hand
you have this ideal of achieving high
performance in your applications
creating a responsive application but
there are a number of various areas in
which you may need to give a little and
compromise there are trade-offs involved
and one of those trade-offs is resource
usage a common technique from producing
higher performance is rather than
recalculating results say cashing it you
use a little more resource keep some
block of memory around and reuse that
you're trading memory for performance in
that case there are lots of other
examples of that with other types of
resources engineering time is always at
a premium there's never enough of it and
there are any number of aspects of your
application that you can spend time
tuning you don't want to spend time
optimizing every little bit of your
application because many cases that can
be premature and it can lead to more
complex code that's just completely
unwieldy to maintain so since
engineering times limited resource you
need to really focus in on where you'll
get the most benefit from the
performance optimizations you're doing
convenience and simplicity a lot of
times though not always the most
convenient way to do something is not
always the way that is conduced
to the highest performance so sometimes
you have to make given and make your
codes a little more complicated to
optimize it for the special case that
it's really going to have to handle so
that you can get maximum performance I
loose coupling flexibility to change
these are the hallmarks of
object-oriented programming and to some
extent you can achieve performance while
still retaining some loose coupling but
but the essence of programming and
developing an application is in some
sense making assumptions defining the
assumptions of your problem domain so
often the more assumptions you deny
yourself the ability to make more
general your code is and the fewer the
optimization opportunities are really
taking advantage of so sometimes you
have to give up a little flexibility in
order to hardwire your codes for the
fastest path for the best performance
lastly strict correctness and safety
almost all the time you want to make
sure no matter what your code is correct
and does the right thing only a few
cases can you actually get away with
approximating but there are some cases
such as when you're drawing if you're
drawing something that doesn't need to
be exact that maybe speed is more
important you can approximate in cases
like that and also in terms of safety if
you're developing generic code let's say
that you're going to put in the
framework and you don't know who's going
to use it yet it might be used by some
unknown application or applications you
might not need to you might not know
whether those applications will need
those objects that you're providing to
be thread-safe let's say so if you have
to provide locking and thread safety
that's a whole other burden that can
affect the performance you have to be
able to balance the desire for
performance against these other
competing demands so there are some
general recommendations we can make
about performance when the most
important things that you can do is to
choose a scalable application
architecture think about the ways that
your application is going to be used try
to anticipate the numbers of objects
that your application is going to have
to deal with and choose appropriate
algorithms and data structures these can
on any platforms can make a huge
difference in how your application
performs also when you're working with
the framework api's don't fight the
framework we try to leverage as much as
possible what the framework provides and
in terms of cocoa in particular
or where cocoa provides a facility to do
something it's in many ways to your
advantage to leverage that facility to
its maximum capability and use it
because then you have the benefit of a
whole team of frameworks engineers at
Apple who are constantly making not only
performance improvements but also
enhancements and functionality and you
get to inherit all of that for free also
wherever possible when you're defining
your own internal data structures and
your own objects try to use API
compatible types where possible you
notice for example in app code api's we
largely use NS array I don't think we
have any api's maybe a few that use NS
sets so if you use a lot of sets let's
say for example in your in your code and
your model code you may find you have to
do a lot of conversions between your
internal representation and what the
framework expects when every time you
make a message send that uses some
particular type of data so try to avoid
that sort of type impedance mismatch by
using API compatible types where you can
also try to keep in mind easily
overlooked cost things like heap
allocations including allocating and D
allocating objects heap operations do
take some time there's some overhead
involved in that we don't normally think
about that we kind of think that it's
just kind of magic we get best for some
memory and get it for free also keep in
mind indeterminate time operations
particularly synchronous operations such
as synchronous file system and network
operations things where your application
is going to block for a fair amount of
time you don't want to freeze up your
user interface obviously while you're
waiting while the users waiting for some
file system or network access to happen
so if you have things like that that may
block for an unpredictable amount of
time try to make those asynchronous and
look at techniques for doing things like
that consider caching expensive results
where that may help if you have
something that you do that you compute
that you draw that may take a lot of
time and you may reuse many times before
you have to change it again it may pay
off the cash it keep in mind at the same
time that it may not pay off to cash it
if the framework say up kit is already
doing some caching for you and you add
your own caching on top of that you may
find that there's not a net win that
you've actually added just another layer
of
of extra code that doesn't necessarily
have any effect so you want to measure
to see whether the caching actually
helps but in general caching can be a
useful technique also lastly consider
deferring operations when you can and
this is an interesting and powerful
technique and so I'll go into a little
more detail think about deferring
operations benefits of deferring
operations include not having obviously
not having to perform an operation
immediately if you don't need the result
right away well maybe you don't have to
perform the operation right away maybe
you just need to make a note somewhere
that hey I need to compute this later
you obviously then don't incur the cost
of the operation right now and if you
have a number of requests a series of
requests that come in maybe from
different parts of the system to perform
that given operation but they don't need
again don't need the results immediately
you may be able to save doing that
operation several times by coalescing
those requests just setting that flag
and every time you set that flag well
it's a constant time operation and it's
really cheap and then later when you
actually need the result you can go back
and say oh I need to compute this and
they're there you've got it and you only
compute it once instead of several times
lastly if you're really lucky you may
never have to incur the cost of the
operation at all if the result isn't
needed so try to think as your coding
about whether what you're computing
really needs to be done is this really
necessary right now on the flip side one
of the cost of this is that when you do
ask when some part of the application
does ask for the result later it's not
going to be there ready to go the
computation has to be done then so
obviously this is you want to use some
discretion and applying this technique
but it is very useful and powerful we
use it for example in the app kit when
we're drawing views you're familiar
probably with the set needs display met
neck mechanism that we recommend rather
than asking views to display themselves
immediately you tell them hey you're
dirty in this area you're going to need
to draw here and you can have
potentially many requests to mark
different parts of the view dirty as
needing display at some later point and
all that drawing doesn't happen normally
until you get back to the run loop to
the top of the run loop and then we look
and say oh it's part of the window dirty
yeah okay we've got to redraw the parts
of the window that are dirty but we can
do it all at once and so we can have you
know thousands of requests to dirty
different parts of the window
satisfied by a single drawing path
similarly in your own applications if
you break your application in two
components if you do initialization in
stages that can be a useful way to to
improve performance by deferring results
operations till later lastly some other
things to keep in mind some of the
techniques that I recommend here or
point out to you rather may not be
appropriate in certain situations may
actually degrade performance and the
only way you can really know although
our intuition as engineers is very
useful the only way you can really know
whether an attempted optimization has
had a positive effect is to measure and
remember to not just measure after but
measure before so you have something to
compare with that's the only way to
really know whether you've made things
better or worse and often the result is
surprising so along these lines we
provide some very powerful tools for you
to use including shark which is covered
in another session any of you who are
here right now and not in the shark
session I recommend looking into shark
on your own later it's a very powerful
tool enables you to sample not just your
application but performance with the
entire system while your app is running
also if you happen to be using OpenGL
OpenGL profiler is a tremendously
powerful tool and getting better so be
aware of these tools and use them to
measure to find out what's really going
on in your apps so okay now we're ready
to get we're in the right frame of mind
we're ready to get into Coco in
particular i'm going to divide the rest
of the talk into two general parts first
we'll look at optimization techniques
and API usage techniques that apply to
the api's that app kit provides and then
we're going to dive deeper and look at
some foundation related issues so first
the app get related stuff things that I
refer to as user interface issues and a
number of techniques I want to cover
with you today including reducing launch
time taking long operations as I was
saying earlier making them a synchronous
optimizing drawing and scrolling in
various ways and so forth so what about
reducing launch time this isn't exactly
maybe strictly an optimization technique
this is a matter more of deferring
optimizations deferring operations until
later you can actually use both tech
niques here remember that the users
experience using your app begins with
launching the app the sooner your
application can be ready to go the
happier your user is going to be so
there are a number of ways you can
reduce lunch time obviously brute force
if there are things that you do at
launch time and you can optimize them to
run faster well obviously that's one way
to do it if you can defer loading of
data initializing different subsystems
if your app has a lot of different
subsystems and maybe your user isn't
going to touch them immediately certain
features they may not use even in the
session of launching the application
using it and quitting it you can
actually defer initialization of those
subsystems if it's costly until later
and one technique you can use for doing
this you can use nib files and plug in
bundles these are two very powerful
capabilities and facilities that that
cocoa provides for factoring your user
interface and your code you can actually
dynamically load code in bundles at
runtime you can factor your application
into components that are loaded in as
needed and that also incidentally
provides it wait for users to for other
developers to be able to extend your
applications potentially so plugins are
great where you perform your
initialization may be important you all
know awake from mid this is our friend
when we create instantiated objects in
lib files and we load those nib files at
runtime awake from nib is sent to every
object at a stage after all of the
objects interconnections as we're made
in the nib have been established and
they're all sort of the network of
objects is intact they're ready to go
and wired up that's usually a very good
and convenient time to perform
initialization one thing about that is
that that awake from mid is usually sent
say for your your main menu nib or for
your document nib if you're writing a
document based app that's sent before
the UI is brought on screen so any
initialization that you do there is
going to make the user wait potentially
if it takes a long time a lesser known
place to perform initialization that's
very useful is application did finish
launching this is a message that is sent
either to the applications delegate if
it has one or two any observer any
object that registers as an observer of
the NS application did finish launching
those
education it is sent significantly after
the UI is already on screen after any
files that were requested to be open
when the app was launched have been
opened and the run loop is ready to
begin handling events your app is ready
to go so if there's if there's
initialization operations you can
perform later at application did finish
launching that's a useful place to hook
in what about long blocking operations
that may block your user interface
remember in a cocoa application by
default unless you do otherwise you
basically have one thread on which
everything happens you're then handling
your drawing any processing that your
application does when I click this
button over here and so forth and so any
long synchronous operation that you
perform on is performed on the main
thread and blocks your user interface
normally so there are a couple of ways
to deal with this we can make the
operation take less time again obviously
or you can use very any of various
techniques to move the operation into
the background and a few of these
facilities that we provide for doing
this are asynchronous notifications
which are also sometimes called idle
timers run loop observers and threads
let's look at those in some detail
asynchronous notifications are simply
notifications that are added to a
notification cue they are n queued with
the posting style of post when idle
these notifications are handled when the
run loop figures out the application is
sitting idle and there's time to do
something it's just sitting there
spinning waiting for something
interesting to happen optionally you can
ask for such notifications to be
coalesced so if there are a number of
different places you want to post them
from to stimulate this idle activity to
happen whatever activity you've
designated to happen in response to the
notification you can you can have them
coalesce either on name or on the
notification sender on the poster
another powerful technique that is
exists actually in core foundation
remember you can use core foundation
from your cocoa apps is the CF run loop
observer and what this lets you do is
hook into not just your run loop but at
the idle time point
at any point you want really and we have
a number of different options here on
entry to the Rome loop beforetimers are
handled and so forth so you can hook in
actually this is a mask so you can
specify several different places that
you want to hook in and it's basically a
mechanism for you to provide a callback
function that will be called when the
run loop reaches these various stages of
processing and then you can do whatever
you want there well these are two very
powerful techniques but obviously
they're still doing their processing on
the main thread there may be a little
more friendly to responsiveness of the
application because they're letting you
do things sort of when the application
figures out that it's idle and if you
don't do your operations and two big
chunks then you're okay but what if you
have a long operation and there's no two
ways about it you've got to find another
way to do it and keep it from disturbing
your UI another possibility is to use a
background thread spin off another
thread this is this is supported through
the NS thread class and in particular is
very easy to do in cocoa the hard part
as with multi-threaded programming on
any app on any platform is getting
synchronization right and synchronizing
object accesses and getting thread
safety but spinning off the thread is
very easy and a thread provides the
detached new thread selector method you
specify the message you want a message
that you want to send the target object
you can optionally specify a parameter
object to be passed in and then here we
have a method that basically is the
thread it becomes the threat when when
thread is spun off that method begins
execution and that method can run
forever if it expects to existed for
wants to exist for the lifetime of the
app or it can quit out if it's done
processing and then that terminates the
thread and one thing that you want to do
here is put model release pool in there
so that any objects that you auto
release during the course of processing
in your background thread do get cleaned
up otherwise you're going to get
messages in the console when you're just
doing main thread program and you get
model release pool for free that's
automatically provided for you so we'll
see that again so as I said the main
thing to do when you are doing
multi-threaded programming you have to
make sure that
no two threads or simultaneously
accessing and especially trying to
mutate a given object we provide locking
semantics for that and another thing
that you need to do when you're doing
the processing in the background thread
obviously your main thread at some point
wants to know about well what were the
results of that processing they usually
affect something on the main thread
maybe some UI and you need a way to
communicate back to your main thread
what the results of that processing it
was and maybe even some intermediate
results so there are various ways to do
that the easiest way to do it is to use
perform selector on main thread this is
a method that's defining a category on
in this object you'll find it it's
hidden away and then it's thread H if
you don't know about it this is a great
powerful method what enables you to do
is not just send a message to the main
thread but but it scheduled your message
so that it's performed it doesn't
interrupt the main thread well that's in
the middle of doing something else and
so you can pass objects across thread
boundaries fairly safely so this is
interesting stuff and to go into this
further let's see what we have cooking
on demo one
so here I have a document based
application that i call scrapbook and
it's fairly simple got my
model-view-controller divisions here
basically what it does is it gives you a
page on which you're able to arrange a
number of photos and you have a page
view that you can use to see that and
visualize it so let's see I've launched
the application and to we have a clean
new document let's drag some pictures in
and it loads them up then we can drag
them around lose them simple document
based application and this is a custom
NS view that I simply wrote that the
displays the photos when it's asked to
draw and the actual page model and the
photo model objects keep track of where
the photos are so let's see I've got an
existing album here did I built and
loading it oh there it goes well that
took a little while try that again drag
the document down and took a few seconds
to do it now we're running fortunately
on a dual proc 2 gigahertz g5 here but
you can imagine easily on a machine
that's more memory constrained or that's
slower that this could take a lot more
time and especially if we we only have
two dozen photos here that's not a whole
lot so how could we make how can we make
this a more satisfying experience for
the user how could we make it so that
can we make it some of the document
comes up more immediately well sure we
can in fact here we have a
multi-threaded version of scrapbook
that's not much more complicated run it
dragged in my document and boom the
photos load in the background and what
I'm doing here it's spinning off a
background thread requesting that the
background thread do the loading of the
images each time I want to load an image
I say here load load this image at this
path and then it does the loading
completely independently of the main
thread and passes back the result when
it's done so that was kind of fun with I
like seeing that was do that again there
we go and we just display a placeholder
image in place of each each image until
the image arrives on the main thread and
if I do this fast enough I can even grab
the Windows Start resizing and
interacting with it while the images are
still loading so let's look at the code
for that it's fairly simple any any
venture into multi-threaded programming
comes with certain warnings about how
complicated it can be to get it right
and to not have your app crash but this
is in fact not very complicated I've got
a background bitmap loader class with
simply a subclass of ennis object that I
created here it's got as its attributes
ape ask you that keeps track of its to
do this is basically it's to-do list
these are the pads of the image files
that I'm supposed to load we've got a
lock that we use to ensure safe access
to the path q so I need any time some
code accesses the pass q it takes this
lock and then release this lock when
it's done I've got a simple flag here to
keep track of whether the thread is
running because I don't start it
immediately when I create the object and
now we've got a delegate here which is
our connection back to the object on the
main thread in this case I've chosen the
document object as the objects that
receives the images as they're loaded so
you create this object with a delegate
you can set its delegate and when you
want to load an image we create one
instance of the background bitmap loader
when we want to load an image we send
this request bitmap at pass message send
it the path and that returns immediately
to the caller but stuck the path in the
queue already and it's on its to-do list
for images to load we also wanted to
have a cancel
qwest message that i can send so that if
we close the document let's say before
all the images are done loading we want
to be able to safely aboard out of this
so that we were not messaging back to a
non-existent document object and then
the implementation the interesting
methods here request bitmap that path as
i as i said we take the lock on the path
cube before we manipulate it we add the
object add the path to the path q i
actually make a copy here to be
completely safe and then I check whether
my thread is running already it's not i
detach a new thread this is the method i
was pointing out on the slide that is
going to send the message load q damages
to actually myself in this case the
background image loader object and I
don't need a parameter in this case I
just want it to start loading all the
images in the queue implicitly similarly
for later when we want to cancel all
requests all we do is take the lock
drain the queue of all the past have
been queued up and release the lock so
this is the work force method of the
thread load cute images we create an
auto release pool here to make sure auto
released objects are cute are
automatically taken care of once the
method exits and then I've got a loop
here where I basically go until I run
out of paps each time through the loop I
look and see if I have another path to
process I got to take the lock on the
path cube before I touch it I grabbed
the next pass in the queue remove it and
unlock the queue because i'm done with
the queue so the main thread is now free
to add request to it and now if i've got
a pass if the queue is an empty now i go
ahead and basically load my my bitmap
image rest using anna status and it with
contents of file and then and it's
bitmap image reps and it was data I
check whether I still have a delegate
and the delegate knows how to handle
this message and I message it back by
sending it because I don't want to just
send it the image it's going to get the
image and say well yeah okay which image
is that I want to send it also the path
to the image so I just wrap those up in
a dictionary let's see I need some code
wrap here
yes I mean now okay that's much better
so I create a dictionary and the only
sort of unusual thing to notice here
that I'm doing is in terms of the normal
object ownership rules usually you hand
something off to another method and you
want to auto release it so that you've
discharged yourself with responsibility
for releasing it when you're passing an
object to cross thread boundaries you
kind of want to make sure that that
objects still going to be around by the
time the the other thread is grab hold
of it and retains and so forth so so I'm
sidestepping the usual rules and I'm
Alec and hitting this dictionary and I'm
passing across using perform selector on
main thread I tell the object on the
main thread the delegate the bitmap has
been loaded and here's the dictionary
that has the path from the bitmap itself
wait until done basically says return
immediately as soon as you as soon as
you register this method to be message
to be sent on the main thread just come
back immediately it's safe to do because
I've already made the bitmap info it's
got to retain count of one it's going to
stick around until the main thread takes
responsibility for it and then that's it
once I've handed off I can let go of the
bitmap the data and the path and I'm
also using an inner auto release pool
here because I may be loading you know
hundreds of images this is an important
point when you're writing a secondary
thread method is that I'm loading
potentially many images I don't know how
many could be hundreds could be
thousands and I could be if I'm auto
releasing things I could end up filling
up memory with all these images that
have sort of been handed off and still
sitting there waiting to be Auto release
so what I want to do is clean up each
time through the iteration you don't
necessarily have to do this every time
through an iteration you might if you're
doing 10,000 iterations do 100
iterations with 100 and have an inter
auto release pool only in the inner loop
but basically every time we hit this
inner pool release we're going to clean
up any auto release objects that were
created during the course of that loop
so that
the most interesting part of it in the
photo object itself where we used to
load the image immediately we simply
load a placeholder image that's small
and quick to load and is only loaded
ones because it's part of our resources
and in the page view itself oops I'm
sorry in the document class itself we
have the bitmap loaded method that
receives this dictionary this is the
other side of the gate and this receives
the dictionary passed off by the
secondary thread hand it off to us we
get the pass in the photo that are in
the dictionary and we basically all we
have to do here is identify the photo
that the photo model object that that
image belongs to and assign it and then
we tell the page view hey you've got to
redisplay this photo because something's
changed about it and that's that we've
got a multi-threaded version of our
scrapbook app without a whole lot of
effort let's go back to the slides that
we could it will be eventually so what
are some other techniques you can use
that relate to app kit components and
facilities this slide wasn't even here
until a few days ago when I spoke to
another developer who reminded me of how
important this is and it's not something
that people I didn't realize that this
isn't something that people usually
think of you want to try to avoid
overusing views user are great and
flexible and powerful they provide a
large number of capabilities maybe more
than you realize you can nest views
within views and every view have in
theory at least this capability although
you don't usually create a control let's
say and give it a subview controls are
usually the leaves of the view hierarchy
but this this potentiality is built in
to NS view at the root class you can
mess use in a tree you can rotate a
views frame this is rarely done but you
can actually take a view and set its
frame rotation and it will rotate within
its super view relative to its super
views coordinate system you can have
arbitrary coordinate transforms with
interview hardly anything uses this
most of the time views use the default
coordinate transform if anything maybe
they'll flip their coordinate system but
that's it they have capabilities to
support tooltips tracking Rex cursor X
all kinds of bells and whistles and of
course we have optimized for the common
cases there are fast paths in the app
gate code for the common cases where
you're not using all of these features
but at the same time just having to
check for whether each given view when
we process it is might be using one of
these features has a certain cost
there's overhead in generality and
having all that generality in the base
class so absolutely do use views and
reasonable quantities there's no reason
to get worried about using views and
your preference panels and controls and
oh do I have too many views here you
know if you've got under a hundred views
in a window that's completely fine
usually the numbers even smaller than
that but what I'm trying to suggest not
doing is using very large numbers of
views maybe hundreds or more one thing
that I didn't do in that application is
I didn't make every photo for example of
view I could have implemented it that
way I could have made it a photo of you
that just draws the photon I could have
made those views sub views of the
document view and maybe that would have
been okay for a couple dozen photos but
if I got into nesting things within
other things if I'm doing a complicated
graphing application it's not the kind
of thing where you'd want to model your
document by having views within views
within views to very large numbers
consider instead using lighter weight
objects it's very easy to derive
something from nsobject teach it how to
draw itself get it give it its own draw
method you can you can define what the
API looks like for a simple object that
just needs to know where it's located
and how to draw itself so I recommend
avoiding overuse of views in extreme
situations that's stressing the system a
bit and will impact performance if you
have custom views that you draw in your
applications in particular large
document views let's say you have a view
that covers a lot of your window and
draws a lot of complicated content you
want to basically be lazy when you're
drawing them try to avoid drawing stuff
that you're not being asked to draw when
your view is asked to display and as I
alluded to before you want to avoid
invoking the display methods we have
a number of display methods on an NS
view that are there for you to use if
you want to really if you really mean
that you want to force a view to display
immediately but doing that has a cost
particularly if you have different
potentially different parts of your
application that are all saying the same
thing to the same view they will cause
it to redisplay itself cause an actual
redisplay of the window in fact or the
dirty parts of the window every single
time that they are invoked instead it's
at all possible defer the drawing you
set needs display in rec and set needs
display if you absolutely don't really
know you know what particular part of
you might have become invalidated you
just want to redraw the whole thing do
that but ask yourself is that the best I
can do whenever you're doing that
oftentimes it's some state has changed
into view maybe only some part or parts
of the view have been affected mark
those individual parts dirty and you
tell us by doing that that there's only
these parts that need to be drawn and
when we call you back we tell you hey
you only need to draw these parts here's
a real simple cheap optimization this is
an easy one liner you can add to your
custom views and the reason for the
existence of the is opaque method i
should point out is that by default app
kit cannot assume that a view will draw
with complete opaque coverage of its
background our aqua widgets are often
non rectangular in shape they have nice
rounded corners and so forth so this is
less an issue of transparency as of
coverage basically this is saying it so
if I'm if I'm not opaque if I'm a custom
view and I'm not opaque I'm saying that
I may need some of the background of the
provided by the views higher up than me
and and the window background to show
through to complete my drawing I only
draw some stuff on top of that if you
don't need the background to show
through declare yourself to be opaque
this saves app kit from drawing all the
stuff behind your view if you've got
this huge document view in particular
that's covering your window making not
if you don't make it opaque we're going
to think okay we got to draw the window
background behind it okay now we've got
to draw your view we don't know that
you're covering it we don't know what
you're doing in your drawer XO be opaque
when you can also when your draw rect
method is called draw minimally try to
draw only what we're asking you to draw
the simplest first level optimization
you can do is simply
use and observe the drug the single
rectangle parameter that is passed to
draw rect that tells you the basically a
bounding box on the area that that needs
display and you can use NS intersect
wreck for example to test against that
bounding box if you have a number of
objects for example here drawable things
that we need to draw in our view this is
a common case for each object before I
go to the trouble of drawing it which
may be costly see if its bounding box
intersects the bounding box that I've
been given if not no bother drawing on
Panther and tiger you can further
constrain drawing beyond that beginning
in Panther we've been keeping a more
detailed accounting than we used to of
specific regions within a view that need
drawing we keep actually we can give you
a if you ask for it a list of rectangles
that are non-overlapping that specify
the area that needs to be drawn so you
can get this directly using the get Rex
being drawn count method if you want
direct access to that rectangle list and
if you use those wrecks you can use the
parameter to draw rect you can still use
that as a bounding box to do trivial
rejection testing you can first check
your objects against that bounding box
if they lie within it okay well maybe
they're in this list of wrecked
somewhere maybe they intersect that list
of wrecks when you drawing alternatively
in the common case like we had before on
the previous slide where you're just
iterating over a list of things to be
drawn you just want to test each of
those things in sequence and not do
anything fancy you can use needs to draw
a wreck which was a new method provided
in Panther that will do all the testing
against this list list of wrecks for you
it will do the trivial rejection test
against the bounding box so use that
that's sort of a more modern alternative
to NS intersects wrecked that we have in
this code sample if you're doing panther
and later you can use that what about
speeding up window resizing window
resizing obviously is partly dependent
on view drawing window resizing is
something that heavily exercises hbu
drawing when the user grabs the corner
of your resizable window and starts
dragging it basically at the worst case
we have to redraw the entire window for
every iteration every step of that drag
if it takes your window longer to draw
you have slower updates then you're
you're resizing is going to chunk along
on the other hand if you if you make the
updates shorter and faster we get a
higher frame rate a much more fluid
motion and much more satisfying user
experience so obviously anything you can
do to optimize view drawing will also
affect give you a payoff in window
resize there are also some further
things you can do specifically for the
case where window is being resized not
just drawing your views more quickly but
preserving view content wherever
possible one thing you can do that's
been around for a long time is the
concept of live resize I think since the
beginning of cocoa a view when it's
drawing itself can check whether it's
being drawn in the middle of a window
resize using the in live resize message
that's highlighted in orange up at the
top there if you are in live resize you
may want to take advantage of the
opportunity to do some less expensive
drawing than you might otherwise do
because you're deciding hey you know if
I'm just going to be drawing a whole
bunch repeatedly real fast you know
maybe I don't need to be pixel exact
maybe I can take a cheaper faster route
and and trade some accuracy for
performance let's say we also have view
will start live resize and you did end
library size or two methods that you can
override to prepare to enter library
size mode and prepare to do any cleanup
you need to do after exit so those are a
couple of additional hooks that you get
before your that all views get this
message sent to them before the window
starts going into library sighs and then
after it ends so if you need to do set
up or clean up that's where you would do
it in addition on tiger we've been doing
some further optimizations to allow
windows to preserve content when they're
resizing one thing you'll notice when
you resize a window oftentimes a lot of
the pixels aren't really changing they
don't need to be redrawn we rendered and
to help support that we've added new API
in tiger on NS window and NS view a
window has the capability to be put in a
mode where it preserves its content
during library size and a view
that is savvy about this mechanism can
implement certain functionality to help
support it to make it possible this is
something that propagates down the view
hierarchy so in order for view to take
advantage of this all of its super views
have to be clued in and know how to use
this mechanism and we're working now
actively to optimize the various using
especially the container views in app
kit that will help support this and an
example of how you would use this if you
have a custom view class my view you
override the preserves content during
live resize method this is a simple
thing like is opaque you're just
returning a bool to say hey I support
this feature I'm clued in I'm in the
know and I know what to do to help
support this live resize mode then you
override usually set frame size or if
you have some other tile methods
sometimes you'll override a different
method to do this but usually set set
frame size is a good point to do this
you ho can you pass on the message to
super first to do whatever normally
would need to be done and then in
addition you can check whether you're in
library size mode this is again that in
library size message if we are in live
resize then what we become responsible
for doing if we have declared that we
support this feature is dirtying the
parts of myself of the view that will
need to be redrawn to accommodate the
new size you know usually and when
you're redrawing in a window if your
views content is pinned to the upper
left corner of the window which is the
case that we support right now then
usually you have sort of an l-shaped
update region on the right-hand side and
the bottom side as the user is resizing
the view larger so you have these two
strips that need to be drawn and in fact
app kit computes these and figures them
out for you so all you really need to do
at this point is get the rectangles from
app can we have this method get Rex
exposed during library size count and
you get back account of a number of
wrecks and you get back two rectangles
it's guaranteed to be never more than
four rectangles
and then you can iterate over that list
of rectangles and mark yourself dirty in
those areas so this is something that
you do only during live resize when
we're not in library size we simply do a
full set needs display to require the
entire view to be redrawn so that's kind
of not so interesting in code let's look
at a demo and see how this works
so I've got my old scrapbook app here
and one of the things you may have
noticed as I started resizing my view is
boy it's really chunking along there I
mean that's just too slow now to be
completely honest in disclosing what I'm
doing here I'm not exactly trying hard
to be very smart about the drawing on
doing we've only got two dozen photos up
here but what I'm doing is these are
full-size images in memory and I'm
scaling them down every time I draw them
so I'm doing sort of an expensive
unnecessary operation if I really want
to make this fast one of the things I
might do is resample the images as
they're loaded down to thumbnail size
since that's all I'm drawing the maps I
don't need the full detail but you could
suppose that you have some other objects
that are complex and expensive to draw
so you know this is just kind of
embarrassing on a dual proc g5 we
shouldn't be chunking along this flow
and in fact if we run quartz debug this
is a useful feature for for growth
display speed measurement if you're not
familiar with it there is a frame meter
in quartz debug that's available from
the tools menu show frame meter and you
can use it as you tells you basically
your number frames per second on the red
gauge there as you move windows around
and as you resize and if we start
measuring this just sort of informally
dragging it around I'm not even breaking
ten frames a second there this is really
embarrassing so what can we do about
this well start by dragging okay
scrapbook to the trash and luckily I
made a copy of it first and we've got
this better scrapbook
and i'll go ahead and hide that and what
I've done in my page view this is my
custom document view class is I have
overridden just as I said preserves
content during library sighs I've got an
I've are for this setting so that I can
show it in the demo but basically we'd
always return yes if we know that we
want to support this feature and then
inset frame size just as I showed you
this is almost an identical snapshot of
the code that was on the slide we figure
out if this option is turned on for the
demo okay yeah we want to preserve
content if we do want to do that we
check whether we're in library size and
just as a paranoia check I checked
whether we actually support this this
message in NS view in case I was running
this on Panther you know remember you
can do these kinds of checks so that you
can take advantage of a feature only if
you're running on the newer version of
the system we get the rectangles being
exposed we mark them as meeting display
and let's see if that was even worse all
that trouble was two methods does the
application here drag the album in we've
got our background image loading
happening and so again flow before if I
go here and turn on preserving content
during live resize look at that that's
just silky smooth now we go up to 60
frames a second so that was significant
in this case
[Applause]
and in fact while we're at it before we
leave the demo machine let's look at
another optimization that I put in there
we've got preserving content but
supposing also that you just want to
take advantage of life in library size
as I was saying to just do simpler
drawing I've gone to an extreme here and
I've got a mode where I can have my view
just draw the photos as simple outlines
just real simple stroke boxes during
live resize mode I enabled that
optimization and as soon as I you know
things are normal when I'm just dragging
things around but as soon as I grab the
window quarter and start to resize
everything changes to boxes and I can
resize all I want obviously this is
extremely cheap drawing so you know I
could be on any old machine even if I'm
on a limited memory I book or something
like that this would be very fast and
this technique is usual usable way back
on earlier versions of OS 10 so you
probably wouldn't want to go to this
extreme I'm sort of doing it to make a
point that you can do very different
drawing entirely different drawing if
you want or you can do somewhat similar
drawing is the more likely case that is
somehow less expensive and approximate
in some way while you're in live resize
mode you can basically you can do
anything you want in this mode and what
I've done to implement this is fairly
simple
there we go
here we have the draw rect method and I
guess we didn't look at this before I'm
doing some of the other recommendation
of other optimizations i recommended
we're getting a list of wrecks that
we're drawing so that when we go to do
our background fill we can only fill
that list of wrecks instead of trying to
fill the whole view we do clip for you
when we're drawing just specific areas
with a view so even if you don't obey
that list of wrecks even if you just
look at the bounding box or just try to
draw you know all over the view we will
clip your drawing just to the area that
that drawing is being requested in so
but you still save something by not even
issuing those drawing commands in the
first place when you have a choice about
it so we're drawing the background fill
using that list of wrecks and the
interesting part here is if I have this
draws outlines in live resize mode
enabled and we're in library sighs mode
then instead of drawing the whole photo
I'll just get the frame of the photo and
i'll just use bezzie a path to stroke
erect and light where it would be and
that's pretty much all we have to do the
only other thing that I need to add is
in view will start live resize and view
did and live resize I need to implement
I need to tell the view to redisplay
itself because when I grab that corner
of the window and I start dragging if I
don't do this then I will get the
complete photo drawing on the one hand
and then as I start dragging new stuff
that appears will be drawn an outline
and that looks kind of inconsistent and
weird same thing when you're exiting
live resize mode you want to redisplay
the whole view at that point to go back
into the old mode and reflect the change
so okay if we could go back to slides
please thank you scrolling not a whole
lot to say about scrolling except that
obviously it hinges somewhat on on view
drawing performance and in some sense
it's related a bit to window drawing
window resize performance because it
exercises you drawing intensively so if
you want if you have a document view
that you're going to put in a scrollview
and you want your scrolling to be smooth
and fluid one thing you can do is make
sure that your document view uses these
facilities that I pointed out earlier
and can efficiently draw small bands of
its
content because that's those are the
kinds of requests that you're likely to
get during a scrolling operation lots of
small little incremental Scrolls where
you just have to draw a little strip and
the more efficiently you can determine
oh I don't need to draw this I don't
need to draw that all this other stuff
that's not in that strip the more
quickly you can draw on the smoother
your scrolling will be another little
feature of scroll view and clip you to
be aware of by default they are opaque
they're set to draw an opaque background
behind the document views of their size
the clip view area size bigger than the
document view you will get opaque fill
all around there for best performance
leave it that way unless you really
really want that outside margin area to
show through to the views on the window
behind because it does significantly
impact drawing performance and scrolling
performance within the scroll view so
that feature is there to use a be aware
that that has a performance cost string
drawing we provide very convenient
methods as categories on NS string you
can draw a string at any point or within
a bounding rectangle and supply a
dictionary of text attributes and we'll
just do that for you I mean that's very
little work to draw a string that's a
one-liner right very convenient but note
that when you invoke these methods to do
this convenient string drawing what we
have to do under the hood to implement
that is potentially set up the text
system wire together the various objects
if you work with the cocoa tech system
you know there's a lot of different
things involved text containers layout
manager and the text view itself and so
forth so we've got it we've got to
figure out how to layout the glyphs for
the string that you've given us and then
we've got to do in the actual
rasterization or drawing of the glyph
this is a lot of stuff going on under
the hood now this is an area that's
being actively optimized we do provide
some degree of caching obviously it's
sort of a general Cashin we can't know
what the behaviors of your applications
will be because every application is
different but we do attempt to avoid
these costs so you should see this
improving on Panther and tigers
continuing to evolve but it may still be
advantageous to use an old technique
that was discussed years ago
and is illustrated still best
illustrated by the worm demo that's
available in dem developer examples app
kit if you're repeatedly drawing the
same strain with the same layout you
need only compute the glyph layout once
right the glyph layout isn't going to
change and so for that reason and others
you can get some performance benefit by
assembling your text objects and hanging
on to them if you just do a little
manual mucking with the text system
allocate your text storage object that
text storage is an object that contains
both the characters and the attributes
applied two runs of those characters
allocate your own layout manager
allocated text container wire them
together and hang on to them keep them
around and use them every time you need
to draw the glyphs then all you have to
do is message the layout manager
directly say draw glyphs for glyph range
and if you play with the worm example
you'll see that this makes a tremendous
difference when you're rendering
especially on older versions of the
system when you're rendering text
repeatedly desi a paths not a whole lot
to say about veggie a paths but if
you're drawing really complicated bezzie
a path I'm talking about paths with
hundreds or thousands of elements be
aware that there are crossing
calculations that have to be done and
the scaling of the algorithms that
necessarily need to be used to get the
closures right and get everything right
with the bezier patch rendering it
doesn't scale too well when you get to
very complex paths so one thing you can
do if you don't need to get if you're
drawing tasks like this that are very
complicated if you don't need to get
exact exact results you can split those
paths into segments and render them in
sequence and it looks like pretty much
the same thing except you get a big
performance win from doing that also if
you have passed that you're creating
over and over again the same path
drawing it over and over again stroking
or filling it keep them around and this
applies in general to a lot of different
types of graphics objects if you create
them and hang on to them there's a lot
more caching that we can do and that
other layers below us like core graphics
can do to recognize oh yeah that's the
same thing I saw before edge you know
draw that again and also you're saving
the object allocation initialization the
allocation
there are a couple of interesting things
to be aware of with relation to NS image
set data retained we have this notion of
where an image is able to reference its
data source rather than rather than
actually containing the image data if
you initialize an NS image using either
in it by referencing file or an it by
referencing URL you will get an image
that for example when it's archived
instead of archiving itself with all the
image data that load from that file it
just archives the reference to the file
or URL and then so that creates a very
compact representation some people use
it for that purpose of the thick and
maintain the reference rather than just
copy the image in but be aware that if
you're drawing that image repeatedly and
you are in particular changing its size
this may cause NS image to go back to
disk or go back to retrieve the image
from the URL every time that it needs to
resize it reek a at a new size to
draw remember NS image does do some
caching in the background that's
transparent to you so if you if you're
using a reference damage and you're
resizing the window and things are
chunking along this is a thing to look
in to check whether this setting is on
and be aware of it and if you need to
resize an image try and you know resize
it once and get the size you want and
leave it that way there's also this
notion of caching separately when you've
used NS images if you've ever dumped a
debug description for an m and s image
you may have seen cached image reps and
you may have seen these they're part of
the public API a cached image rep is a
representation of an image that usually
is generated from something else and
drawing you've captured from a view or
from resizing an image that you
originally loaded as a bitmap cached
image rep is an image that lives in an
off-screen window somewhere and that's
how where the window server is keeping
it you know in a screen compatible pixel
format usually you know ready to go this
is sort of an optimization keeping these
cached images now by default an image is
not cached separately in its own window
we have the possibility for NS image is
allowed to cash images together and to
just sort of grow this shared window or
shared windows as it sees fit when it
needs to load additional images if you
have large transient images that you're
loading using NS image and load them and
then they go away and you're not going
to replace them with another large image
and you want to avoid the potential
resource usage cost of this cache window
growing and growing and growing you may
want to set set cached separately to yes
to force the use of a separate
off-screen window for that particular
image that image will stand alone in the
off-screen cash at the windows server
that NS image uses so this is a good
strategy to use for transient images so
that's issues that generally pertain to
app kit api's let's dive in deeper now
and look at things that are more at the
foundation level in particular we'll
look at notifications these are
venerable and widely used and still very
powerful and important mechanism and app
kit ways of accessing collections and
strings are sort of a kind of collection
more efficiently using Auto release and
the memory management mechanisms that
obviously and cocoa provide the
immutability concept and also techniques
for working with property lists so
notifications first of all notifications
are a very powerful and flexible
mechanism for a given object to
broadcast volunteer information about
some interesting event or potentially
interesting event that's occurred in it
to any number of other observers that
the object isn't aware of the observers
don't need to be aware of one another
they can all register with the central
Notification Center and the Notification
Center mechanism provides sort of the
loose coupling that allows all these
objects to communicate about a
particular topic we're not necessarily
knowing about one one another as an
observer you can subscribe for specific
notification posted by specific object
or you can cast wider nets and ask for
all notifications posted by a given
object or all instances of a particular
notification regardless of the object
that posted them
you can also use the distributed
notification mechanism to pass
notifications across process bounce is a
very powerful general facility it's been
in the app kit since the beginning but
there are certain performance issues
associated with its use particularly
abuse one thing to be aware of is that
when you post a notification there's a
cost there even if nobody is listening
obviously we've tried to optimize this
to make it a fast pass within the
notification centers dispatch mechanism
but even if nobody's listening there's a
certain cost involved in checking to see
whether anyone is listening and this
gets to be a problem in particular if
you are sort of speculatively writing
classes if you're writing code maybe
that's going to go in a framework once
again that's sort of intended for
general reuse by potentially unknown
clients clients you may not know about
right now you may not know well what
sorts of of notifications might they be
interested in what kinds of changes in
me might they want to know about so you
get into this problem of sort of
speculative posting you you tend to err
on the side of caution and post
notifications for all kinds of things
potentially just in case somebody might
be interested in listening for that
that's a bit of a problem obviously
because of the cost of posting even when
nobody is listening these kinds of
things add up so try not to try not to
speculatively post try to restrict your
usage of notifications somewhat to
really sort of important things that
need to be broadcast also note that
repeatedly adding and removing observers
from the notification center forces its
dispatch tables to churn and and that
has a performance impact too so in
particular if you have transient
short-lived objects and you're
manufacturing these and discarding them
in large quantities in your application
and each of these objects maybe is
observing a notification or maybe
several notifications when they get
created they add themselves with an
observer to the notification center when
their dialect they remove themselves
these objects are coming and going a lot
you're hitting the notification center
pretty hard and this may start to turn
up in your samples in shark so consider
consider alternatives to that and we'll
look at some also note that for the
general case of local notifications
usually you're not using distributed
notification
these are handled synchronously when a
notification is posted control does not
return to the poster until every single
observer of that notification has been
found and has been given in series it's
chance to execute whatever it code it
has to execute in response receiving
that notification depending what you're
doing depending how many observers there
are obviously you as the posting class
you may not know about all the observers
or how many they're going to be this
could be costly this can block your
application for a fair amount of time so
what can you do if you are going to use
notifications to get around this in
general help us help you be selective as
much as you can about what kinds of
events you really need to broadcast to
the world be as specific as you can as
an observer when you're registering for
notifications if you know the name of
notification you want and you know the
object register using a non nil name and
object these types of entries in The
Dispatch table or in general easier for
us to optimize and find find quickly to
do the efficient dispatch you may want
to if you have several notifications for
a given object you can add yourself as
an observer with nil is the name and
just the object you can also remember as
an alternative to that you can add
yourself several times as an observer
for once for each notification depending
what version of the system you're
running on that may or may not be a
performance advantage so measure to see
if that's an issue also obviously the
notification handler methods that i
mentioned that that block while they're
processing and don't return control to
the poster until they're done obviously
make these as efficient as possible and
if they don't have to do work right away
and you can defer it defer that work and
as I said avoid repeated removal and
addition as observers if you possibly
can one way around this is to consider
using a longer-lived intermediary object
that keeps track of all of these
transient objects and directly messages
them when it receives the notification
as an example of this let's say we have
an originator object that volunteers
some information about itself by posting
a notification it posts a notification
of the Notification Center and if we do
this in a really simplistic way we'll
have all these transient objects here
that are coming and going they're adding
themselves as observers to the
Notification Center they receive the
notifications directly from the
notification center when they are posted
an alternative to this is to drop a
longer-lived object in the middle there
and have it be the only observer that is
added to the notification center it's
maybe some object that knows about all
of these other transient objects and can
more efficiently dispatch this
information to them by say sending a
simple ibc message to each object you've
got less overhead there because you've
got fewer entries in the notification
center dispatch table at any one time
and also you're not adding and removing
them and turning that table over and
over again so this is one technique you
can use if you're seeing a lot of load
in the notification center consider
using intermediate objects it's an
alternative to using notifications you
may not have considered key value
observing and key value binding these
are powerful technologies that were
introduced in Panther that basically
enable you one object to observe value
changes in an attribute of another
object or bind one of its attributes to
the attributes of another object you may
not have thought of these as
alternatives but really they're just
sort of another way of propagating
changes through your application one
thing one advantage to this approach is
you don't have to anticipate what others
might be interested in you just have to
make your your accessor methods key
value coding compliant there's no
performance penalty for unobserved
changes because you're not posting
trying to anticipate whether others may
be interested in there's there's no
overhead involved for an object whose
attributes aren't observed or for a
particular attribute that isn't observed
to change so obviously that lists the
burden from you too of having to figure
out what others might be interested in
and it facilitates a similarly loose
coupling to what notifications provide
I'm not saying that notifications are
going away by any means they're
hardwired into a lot of what we do in
the kit we have notifications that
objects advertise they'll continue to
provide those like nfcu provides view
bounce to change view frame did change
notifications when it's geometry changes
there are lots of things that are built
on that
but if you're writing new modern code
you might want to consider KBO and KBB
is another way to go if notifications
aren't appropriate performance wise also
obviously if your problem is simpler and
you don't have a whole lot of objects
you have to notify consider the
delegation pattern that we use
throughout the app kitten foundation
just have an object have a delegate that
it passes off a message to the simple
objective c message sent to a single
receiver with the same cost collection
access you may be able to infer a lot of
the performance characteristics of
accessing collections from the general
character of those collections obviously
there are some things you can perform
efficient random access is on like a
raise if you iterate through an array
doing linear search you know same thing
is on any other platform that's a no
event operation for sequential access
through objects we do provide NS
enumerator it's a very convenient sort
of collection agnostic mechanism that we
provide for sequential odd object access
within a collection so you can you can
ask an NS array or can be an unordered
collection like an NS said you can ask
any of the common data collection types
that we provide for an enumerator for
their objects and then you just talk to
be immune to mer ater communicate with
it to enumerate the objects in the
collection there's potentially more
overhead involved in doing this there's
more in direction than doing direct
element access and it may you may find
that it affects object lifetimes and you
may start to see objects showing up in
the auto release pool when you're using
enumerators so there are convenience and
I certainly don't want to steer you away
from using them in the general case in
most cases this is the best way to do
things but in cases where overhead is
important as you're iterating through a
collection of objects you may want to
consider alternatives one alternative is
to is be make objects perform selector
with object method we also provide a
version of that with no object parameter
you can message a collection and have it
send if you want to send the same
message to a whole bunch to all the
objects in a collection you can use make
objects perform selector to more
efficient
do that dispatch because the the
collection class does that for you and
it's familiar with all its data
structures that can access the internals
and do that a bit more efficiently so
that's a useful thing to know about also
in court found a shin there's a
comparable facility the CF apply
functions there's a CF array applies
function CF dictionary apply function
and so forth for the various collection
classes that core foundation provides
and they basically give you the ability
to specify callback function instead of
a method that's executed for every
single object in the collection if you
have an ordered collection and
specifically an NS array you can use the
object at index method and get objects
range to directly access the elements
much more efficiently than you might be
able to with an enumerator and in
particular if you're doing random access
this is the only way to really do it so
if we're iterating through an array
using using these methods instead of
using an enumerator one thing you want
to remember to do is cash your account
and in fact this goes for anything that
any properties of an object that are
invariant over the course of an
iteration move the getting of those
values outside of the iteration and you
save time and other languages that you
may have used this may not be as
important but the objective c compiler
is not able to to know that for example
if i had written i less than bracket an
array account bracket there the compiler
would not be able to know we have no way
of knowing that the count is not
changing it's got to send that message
every time it checks the determination
condition for the for loop so keep that
in mind if they're their messages that
you send to get invariant quantities
cash those instead of using them each
time through the array instead of asking
for them each time calling a method
through an in I don't know how many of
you are familiar with this technique
it's been around for a long time it
should be used with care as far as for
one thing it breaks polymorphism it only
works when all of the objects that you
are sending a message to our in general
of the same type actually they really
just need to have the same method
implementation so you
maybe a common base class would be
sufficient for a given message that you
want to send them what what an imp is is
an instance method pointer a pointer to
an implementation method for a
particular message you can get that and
then send use that as basically a
function pointer to call that method
directly and this is particularly useful
where you have a whole bunch of objects
with the same type and you want to
really minimize the overhead of
communicating with them here in this
code sample we say have a do something
useful with message that we want to send
to all the objects in an array first
thing we need to do is get the IMP for
that message and I take the object at
index 0 as an arbitrary object in the
array I'm assuming that this is a
heterogeneous collection I mean a
homogeneous collection rather excuse me
and these objects are all are going to
have the same implementation this code
will break otherwise so I get the the
IMP pointer so basically now I've got
the function pointer i got i don't have
to go through the odd seed dispatch
tables for four methods i just call that
function method directly as if it were a
function so i get my my town of the
objects from the array and i'm also here
using its not highlighted but i'm using
the get object method which enables you
to retrieve at once a whole bunch of
objects in fact in this case all of the
objects out of an array there's also a
get object variant that has a range
parameter that lets you specify a sub
range of the array whose and elements
you want to get so here what we're doing
is we're getting all of the objects out
and we're malaking memory block to hold
the is the the instance pointers for all
of these objects so that we have them in
a standard C array and then we can
access them any way we want it's not
essential in this case but but it's
something that might be useful if we
wanted to do other really fast random
accesses so we get the objects out and
then for each iteration all we have to
do is call through the function pointer
and the important thing here to note is
the first two parameters when you send a
message to an object using the usual odd
C syntax the target is specified as the
first thing in the in the brackets and
then the selector is sort of collected
together
as the message to send different
selector elements if you have more than
one parameter these are sort of implicit
parameters to the method implementation
that obviously usually hides from you so
when you do call through an imp you have
to provide those yourself so it's the
first parameter to this imp that we're
calling we provide the object that's the
target of the message we provide the
selector just in case the IMP wants to
check these because it can it can ask
for it obviously obviously is going to
need self and it might actually check
the command that is being sent to it the
selector so we send those and then we
provide an argument here because in this
case our do something useful with needs
something to do something useful with so
that was collections in general string
operations are in some sense sort of a
special case I mean string is really
just a collection of characters right so
a lot of the same conceptual ideas apply
in particular you want to avoid
intensive character by character access
to a string as you can character it
index is there if you just need to grab
a character to out of a string once in a
while but it's not really intended for
heavy use and again this is the kind of
thing that the objective c compiler
can't really in line these types of
things it needs to send a message each
time in order to actually get the
character so as alternative look at all
of the methods that NS string provides
the big header one of the bigger headers
we have and they're all kinds of useful
methods that can directly access the
internals of the string and and can do
more efficient operations if you're
looking for substrings doing searches
through a string and so forth and or
doing mutations mutable strings try to
use the end of string methods when
there's one available when you're doing
sequential scanning or parsing through a
string searching for sub strings and
wanting to keep track of your position
in a scanner remember in a scanner it
provides a whole bunch of methods for
doing that and if neither of those
facilities provides what you need you
might just want to fetch the characters
in blocks and you can use the yet
characters range method to do that
similar thing to what I was doing with
get objects for NS array you can
get the all of the characters out of
Unicode characters into a standard C
array and then you can do whatever you
want with them including using C
functions and so forth object management
and responsibility responsibility for
objects is a big part of any object
oriented application and cocoa
applications are no different we provide
a number of convenience methods factory
methods class methods on various objects
for very quickly and easily with very
little code getting back an auto
released object it's important to
remember that these are Auto release by
convention when you use a factory method
so if i call NS mutable array array I'm
getting back an array that has been auto
released so it's been added to the auto
release pool for the current thread and
it's going to have to be processed by
that auto release pool later this is a
lot more convenient than writing say NS
mutable array alloc init and then doing
an NS me remembering to release the
array later it's less code and so it's
convenient and it's ok to use in the
majority of cases and less performance
is really being impacted if you have
large numbers of objects or large
numbers of iterations that you're
dealing with also remember and also
remember that there's no need to auto
release a temporary object that you're
using just within a method body is
something you're not returning back from
the method you're just creating it and
you're going to use it temporarily to do
some operation get rid of it that would
be a case where you can alloc init and
release instead again not in every case
is this really necessary but you know
this becomes a significant fraction of
what you're doing you might want to
consider that keep that in mind this is
this is obviously most worthwhile for
objects that are created an auto
released in large numbers and it avoids
potential spikes in memory or resource
usage remember when an object is auto
released all these Auto release objects
start to sort of pile up and stick
around because they're waiting around to
be disposed of when the auto release
pool is cleaned up by default unless
you're creating your own auto release
pools that's when your code kind of
finishes up and returns control back to
the to the run loop usually after
processing a user input event or
something like that you go back to the
run with all that stuff gets cleaned up
but in the end
if you're doing a whole lot of
operations from creating a lot of auto
released objects and maybe a lot of big
auto released objects if you're doing
say image processing or image loading as
I was doing you can get spikes in memory
usage where instantaneously you get
peaks in your usage of memory so for
example that's the kind of thing that
you would see using object Alec one of
the performance tools that we provide
for you and it's also a useful debugging
tool so we have an application here and
I'll magnify where we've gone through a
loop and we've basically allocated
10,000 auto release strings real simple
trivial example we have a current count
of only a thousand and 97 strings but at
the peak we had about 11,000 strings
that we're running around in this
application hanging around waiting to be
Auto released at the end of the loop and
as we got back to the main run loop so
this is what not o rly spike looks like
when you see these long bars in object
Alec you may want to look at what your
auto release patterns are and whether
there are cases where you could use
additional auto release loop auto
release pools to provide clean up before
memory usage gets out of control it's
not a big deal if you're not using a
whole lot of memory but if you're really
using big chunks of memory remember
you're sharing the system with a whole
bunch of other processes or their
applications and if memory is tight you
may start swapping and thrashing and
then it becomes a performance issue so
again an applications main thread has a
default auto release pool that's
provided for you when you auto release
you don't have to worry about it just
goes to that you can bracket operations
with your own auto release pools as
we've already seen reference counting
when you every object every nsobject has
a reference count that determines how
many other objects are retaining it you
get a retain count of one when you
allocate and in it your object you go
back to when you go back to retain count
of zero the object is deallocated that's
how we know when to get rid of it and
retain and release and auto release so
the mechanism for maintaining that
that reference count for historical
reasons the reference count itself isn't
really an instance variable that's
stored directly in the object it's
stored off somewhere else in a table and
when you do retain we do a release
that's under the hood that's that's
going to go access something somewhere
else so if you have very heavily used
objects and by this I mean objects that
are created in large quantities and are
often retained and released above and
beyond the default reference count of
one that you get when you Alec if you
don't just allocate and get rid of it
but y ellicott and there's some
retaining and then some releasing and
then you finally get rid of it if you
have heavily used objects like this it
may be to your advantage to implement
your own retain release and retain count
methods and what you can do is simply
add a reference count variable an
integer unsigned value to your class and
here we have a my object class that does
just that then you implement retain
count to return your retain count in
place of this user overrides of the
standard nsobject methods we override
retain count to return your retain count
instead of the one that nsobject
normally maintains and in this case
we're using the convention that my rest
count the I've are actually holds one
less than the retain count so it's
initialized to zero that's just sort of
a convention we're using here and so
when we want the actual retained count
we add one to that then when you get a
message to retain you increment your own
reference count I've are that you're
keeping track of and we return cells
because that's the assumption for retain
then when your object is released you
check whether you're your own reference
count has reached 0 and you d alec
yourself otherwise if you're not quite
20 you just diplomat your reference
count so this is not something to go out
and do for all your objects but it's
something that is a useful technique for
heavily used objects not a whole lot to
say about immutability except be aware
of the concept and use immutable objects
immutable variants of objects where we
provide them and where you don't really
need to be able to mutate an object
after you create it that facilitates
certain optimizations and potential
optimizations we can do another thing
that you'll see that's common practice
is returning and you'll be encouraged to
do is to return immutable I've are even
if it
mutable as immutable you can do this for
example if you have this this class
myclass that's got a mutable array as an
i varsity track of a list of objects
it's children and it can be asked to
return those by typing the the children
access or as NS array instead of NS
mutable array we're sort of we're making
a contract with the client that we're
going to return you an NS array and as
far as you know you can't mutate it so
this saves us from actually making an
immutable copy of the Ray the array when
we return it so we could have our
children method they're doing a copy
auto release instead but obviously
that's overhead that's performance
impact so by doing this you this is the
usual practice it saves copying an auto
release little activity it does sort of
violates tricked encapsulation because
someone could get hold of that array
realize it's mutable and do something
with it but that's malicious and
hopefully you won't be doing that to
your own data structures also note that
if you're implementing your own
immutable model objects you can
implement the copy operation to simply
do a retain if there is if your receiver
is immutable copy can be made equivalent
to retain because really how many copies
is something that's not going to change
do you need you can just sort of pretend
to copy the same your hand back the same
thing and that way you don't have a
whole bunch of inflated memory usage in
your apps it's sort of a way to provide
singleton instances in a sense in the
design pattern sense within your apps so
we're almost done here one last thing I
want to point out to you we have a
binary format for property lists you've
all seen the XML format property lists
provide a convenient way if you have
data structures data storage that uses
dictionaries arrays strings and all the
sort of standard foundation types they
provide a convenient way to serialize
those out save them out to disk and so
forth the XML format is great it's human
readable you can go in and hand edit it
if your user has corruption in a
document they can send it to you and you
can read it and say oh this is where
this went wrong but we also have a
binary format that is smaller it is
faster to read and parse
it is very easy to specify it when
you're serializing out your data it's
just another parameter and we have a PLU
till command-line tool that you can use
to convert back and forth between binary
and XML all you have to do is specify
the format to be binary when you're
writing out your when you're serializing
your property list using NS property
with serialization api's that's it and
then you get a binary representation
handed back to you instead of the
textual xml representation when you're
reading back that data you don't even
have to worry about it it doesn't matter
NS property list c realizations api's
we'll figure out for you whether it's
being handed some binary or textual xml
property list data so if you want that
information is available to you it will
tell you oh that I open this document it
was it was XML or this was binary but
you don't even have to be concerned with
it when you're reading the files back so
that's it for foundation technique just
some quick conclusions to help you
optimize better know the framework
that's that's the I hope to take home
lesson today and I hope I've provided
something new for everyone with respect
to that know the functionality it
provides know where there are different
ways to perform a different task
different given tasks that may have
different performance characteristics
work with the framework wherever you can
wear appropriate look beyond the
framework remember we have a lot of
facilities available to co collapse
things like core data and court score
you've heard about at the talk here
opengl is the fast path for the hardware
for for rendering not just 3d but also
to d we have accelerate framework and
encapsulate altivec based image
processing and general computation and
remember use the provided performance
tools to measure because if you don't
measure you don't really know what's
going on and you may actually waste time
optimizing in areas that you think a lot
of time is being spent in but you may be
surprised to find that time is really
being spent elsewhere and you could
spend your time more productively for
more info we have documentation about
performance in general and drawing
performance in particular that you might
want to look at on the ABC homepage
Matthew Formica is
tact for any questions that you may have
subsequent to this conference