piaoyongren / core-plot

Automatically exported from code.google.com/p/core-plot
0 stars 0 forks source link

Support non-core animation drawing mode #126

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Core animation is good for many things, but does have its drawbacks. Layers can 
use a lot of 
memory and drawing performance on iPhone can be slowed by many transparent 
large 
layers.

One solution to this might be to support different modes of drawing. Rather 
than make 
CPLayer a direct subclass of CALayer, it could just have an Ivar for its layer. 
This is like how 
NSView and UIView work. CPLayer would effectively be layer backed.

In standard mode, the layers would be rendered as they are now, but they could 
also be 
rendered directly into a graphics context, without layer backing. This could be 
a big 
performance win on slow hardware, at the cost of being able to apply animations.

Having layer backed CPLayer would also make it possible to substitute a tiled 
layer in some 
instances. This is not possible with the current setup.

Original issue reported on code.google.com by drewmcco...@mac.com on 26 Apr 2010 at 6:19

GoogleCodeExporter commented 9 years ago

Original comment by drewmcco...@mac.com on 2 May 2010 at 3:24

GoogleCodeExporter commented 9 years ago
This is absolutely needed for the library to be used on the iPhone. I don't 
mind loosing the ability to perform 
animations, but the library is too slow to be used as real time data presenter. 
Having the possibility to quickly 
render it all in a single graphics context would be a huge usability 
improvement for many performance critical 
applications.

Original comment by carbon...@gmail.com on 23 May 2010 at 9:49

GoogleCodeExporter commented 9 years ago
It won't help the memory usage, but you should be able to use the 
layoutAndRenderInContext: method on any 
layer including the graph right now. Just maintain the graph normally, but 
don't add it to the view hierarchy.

Original comment by eskr...@mac.com on 23 May 2010 at 1:53

GoogleCodeExporter commented 9 years ago
Thank you for your answer. I tried to draw the graph on a image context using 
layoutAndRenderIncontext: 
(thanks for the tip) and then put that image on a view. However, I do not see 
any performance improvement in 
doing so for what I need. What I need is being able to quickly change plots 
from a continuously moving data 
source in real time. Is there a way I can achieve more performance to do so 
with core-plot. I am not changing 
axis or backgrounds, only plots, but unfortunately they seem to take forever to 
render. Thanks for any tip.

Original comment by carbon...@gmail.com on 23 May 2010 at 3:03

GoogleCodeExporter commented 9 years ago
How much data are you rendering, and how often are you refreshing? You may be 
better trying to reduce the 
refresh rate, perhaps by skipping some refreshes. In this case, it may not be 
core animation that is the problem. 
That is more an issue when you have interaction.

It would be very useful if you could run your app on the iPhone with 
Instruments to check where the time is 
being spent. Saving the instruments data, and perhaps uploading it here may be 
useful to finding out what the 
issue is.

Original comment by drewmcco...@mac.com on 23 May 2010 at 3:28

GoogleCodeExporter commented 9 years ago
I am refreshing once every 2 seconds, this is long enough for core graph to 
keep doing its thing but it happens 
that the time it takes to render makes the iphone unresponsible for a while. I 
am currently testing it with a 
NSArray per plot which does not change and using the faster numbersForPlot data 
source method, so the issue 
should not be the data feed at this time. I am rendering 3 graphs with 2 XY 
plots in each and 60 points per plot. 
Even with no plots, the time it takes to render the 3 empty plots makes the 
iPod noticeably freeze for half a 
second or so every 2 seconds. I wonder whether I could render on a second 
thread just to make the iPod feel 
more responsible while the graphs are on. I will run instruments later to see 
if I am able to provide some 
additional light on this. Thanks,

Original comment by carbon...@gmail.com on 23 May 2010 at 5:49

GoogleCodeExporter commented 9 years ago
I did the following to test it for performance. Added NSLog statements to my 
calls to reloadData, the end of numbersForPlot delegate, and the end of  
CPLayer drawInContext. This is a fragment of the log:

2010-05-24 11:25:01.137 ScadaMobile[6469:207] Charts View reloadData
2010-05-24 11:25:01.141 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.145 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.150 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.154 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.159 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.163 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.168 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.172 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.200 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.208 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.218 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:01.227 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:01.353 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x276030> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:01.470 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x275f40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:01.599 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272f00> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:01.716 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272e10> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:01.844 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x270150> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:01.970 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x26f420> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.137 ScadaMobile[6469:207] Charts View reloadData
2010-05-24 11:25:03.141 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.145 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.150 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.154 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.159 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.163 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.168 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.172 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.200 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.209 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.218 ScadaMobile[6469:207] Chart View numbersForPlot field:0
2010-05-24 11:25:03.227 ScadaMobile[6469:207] Chart View numbersForPlot field:1
2010-05-24 11:25:03.367 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x276030> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.484 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x275f40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.612 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272f00> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.730 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272e10> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.858 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x270150> bounds: {{0, 0}, {320, 146}}>
2010-05-24 11:25:03.977 ScadaMobile[6469:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x26f420> bounds: {{0, 0}, {320, 146}}>

It can be seen that the iPod takes about 0.850 seconds from the reloadData call 
to the end of the last render. Also the time from reloadData to the 
end of numbersForPlot is 0.090. So it seems the bottleneck here is mostly 
rendering.

I attach also an Instruments file. It is interesting (although a bit 
frustrating) to see that there is not a single place where optimizations can be 
effectively done. Actual rendering is only about 8.2% (if I understand it well) 
and operations with NSDecimalNumbers take a lot of time all over the 
place (at least 20%), because they seem to be implemented using strings 
internally, which is what I think is really killing the library.

I will appreciate any comments.

Joan

Original comment by carbon...@gmail.com on 24 May 2010 at 10:03

Attachments:

GoogleCodeExporter commented 9 years ago
It actually looks to me like there was a bug in there, causing decimals to be 
used when doubles should have 
been used. If you are using NSNumbers, there should be very few NSDecimals used.

I have fixed and pushed, so update and see if that helps, at least with the 
NSDecimal methods.

Original comment by drewmcco...@mac.com on 24 May 2010 at 6:15

GoogleCodeExporter commented 9 years ago
Thanks for having looked at it. It has been a significant improvement. Now it 
takes 0.548 seconds to plot the 
three graphs instead of the previous 0.850. That's 55% faster, not bad. It has 
become certainly much more 
usable, the freezes on the interface are now less noticeable. Just for 
curiosity I will study it a bit with Instruments 
to see where the time is being taken now and will post it later.

Joan

Original comment by carbon...@gmail.com on 24 May 2010 at 7:18

GoogleCodeExporter commented 9 years ago
Ok, I looked at it and squeezed more performance out of it. The same test runs 
now in 0.268 seconds compared to the original 0.850 and your fix at 0.548. Find 
changes 
attached. What I just propose is two simple changes to avoid unnecessary 
NSDecimalNumber calculations and conversions when the data sources are floating 
point numbers. 
I believe there is still some of room for performance improvements, the library 
is still somewhat killed by allocations/deallocations, may be extensive use of 
auto-released 
objects, and some redundant method calls inside loops. This would be much 
harder to fully optimize because in some cases it would require different 
coding patterns. I am 
happy with the current result, but may propose additional optimizations if I 
feel they are not too tedious to implement and are significant. Just for your 
information I copy 
below the logs running the same test app with the proposed changes.

2010-05-24 23:55:25.657 ScadaMobile[6705:207] Charts View reloadData
2010-05-24 23:55:25.661 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.665 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.670 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.674 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.679 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.683 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.688 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.692 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.706 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.710 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.714 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:25.720 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:25.748 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x276090> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:25.782 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x275f90> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:25.837 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272f40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:25.863 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272e40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:25.901 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x270170> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:25.926 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x26f430> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.657 ScadaMobile[6705:207] Charts View reloadData
2010-05-24 23:55:27.661 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.666 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.670 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.674 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.679 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.683 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.688 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.692 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.697 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.701 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.706 ScadaMobile[6705:207] Chart View numbersForPlot field:0
2010-05-24 23:55:27.710 ScadaMobile[6705:207] Chart View numbersForPlot field:1
2010-05-24 23:55:27.749 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x276090> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.785 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x275f90> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.832 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272f40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.863 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x272e40> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.900 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x270170> bounds: {{0, 0}, {320, 146}}>
2010-05-24 23:55:27.925 ScadaMobile[6705:207] CPLayer drawInContext: 
<<CPScatterPlot: 0x26f430> bounds: {{0, 0}, {320, 146}}>

Joan

Original comment by carbon...@gmail.com on 24 May 2010 at 10:04

Attachments:

GoogleCodeExporter commented 9 years ago
Sorry, there was a bug in one of the files I attached on my previous post. Here 
is the correct one.

Thanks,

Joan

Original comment by carbon...@gmail.com on 24 May 2010 at 10:28

Attachments:

GoogleCodeExporter commented 9 years ago
I've incorporated some changes 'inspired' by yours. Not exactly the same, but I 
think they will lead to a good 
performance boost. 

Original comment by drewmcco...@mac.com on 25 May 2010 at 8:49

GoogleCodeExporter commented 9 years ago

Thank you for committing changes. However there were a couple of performance 
improvements in my code 
that you did not incorporate. Current performance is not much better that the 
one we had after your fixed the 
earlier bug. I will try to explain why. All changes refer to CPScatterPlot.m

1- In calculateViewPoints you should have self.xValues and self.yValues in 
local vars *outside* the for loop. 
This is a huge performance boost because it avoids accessing the cachedData 
dictionary by key in CPPlot and 
the creation/release of the NSNumber key for each point. Currenlty, a 100 
points plot is accessing 200 times 
an object in a dictionary by key.

2- The same applies to calculatePointsToDraw. The self.yValues and self.xValues 
calls should be out of the 
loop. If not, this adds 200 additional accesses to the dictionary.

3- The compareToNumber calls in calculatePointsTodraw also took a lot of time 
in its previous 
implementation. Current implementation should be a bit slower than mine, but 
should be ok, and does not 
add another method.

A developing pattern in Objective-C I always recommend is to use local 
variables to temporary store objects 
that may be far than where they are used. Not doing it when loops are involved 
hits very seriously 
performance. Objective-C is not like C++ where the compiler has a lot of info 
to optimize function calls. 
Objective-C can be so dynamic that to be safe the compiler has no other choice 
than to generate separate 
code for every and each property access or method call. On top of this 
Objective-C calls are significantly 
slower than C or C++ calls. This brings some responsibility to the developer, 
who should be aware of it. This 
is why a simple measure such as using local variables to avoid unnecessary 
calls prove to give big 
performance boosts in many cases.

Proposals 1 and 2 are very easy to add, and they cut processing time for a 
simple XY Plot by as much as a half. 
I will appreciate that you incorporate them to avoid having to maintain a 
separate branch myself for my use. I 
really need the added performance they give. Thank you very much.

Joan.

Original comment by carbon...@gmail.com on 25 May 2010 at 1:45

GoogleCodeExporter commented 9 years ago
Ah, sorry, missed the other fixes. They were a bit subtle. 
They are in there now.
Thanks, and good work. If you think you want to contribute regularly, we can 
arrange commit rights.

Original comment by drewmcco...@mac.com on 25 May 2010 at 2:00

GoogleCodeExporter commented 9 years ago
Sorry, I just realized that you already incorporated what I mentioned in my 
previous post, please forget about 
it. I must have been looking somewhere else. My apologies. It's really 
fantastic you respond that quick to user 
proposals.

Just we could also add NSUInteger locals replacing all uses of 
self.xValues.count on for loops. It is being called 
as many times as the loop iterates, so removing it may well squeze a bit more 
of performance, and it is very 
easy to do. I will post the definite performance figures in case you decide to 
do so. Thanks again.

Joan

Joan.  

Original comment by carbon...@gmail.com on 25 May 2010 at 2:14

GoogleCodeExporter commented 9 years ago
Core-Plot is really well thought in general terms and its clever class 
structure makes it easily expandable. That 
tells a lot about who designed it.  However its support for NSDecimalNumbers 
and its reliance on NSNumbers 
makes it too slow for some uses. After the last proposed changes simple 
plotting speed has nearly doubled. 
According to the comparative tests based on what I described on a early post we 
are now at 0.280 seconds, 
from the original (buggy) 0.850 and its fix at 0.548 s.

By going back to the files I posted and eliminating any remaining redundant 
calls to self.xValues.count I 
checked that we can squeeze another 0.038 seconds, so we would be at 0.244. 
This is the easiest thing to do.

However, after looking carefully at it I discovered that there is still, as 
unbelievable it could seem, a huge 
margin for improvement if we could get rid of all the NSNumber stuff. In order 
to have this option for really 
performance demanding applications I propose to split the cachedData storage in 
a way that is able to store 
NSData objects (as oposed to NSArrays) when the data source is a C array of 
doubles, as well as maintaining a 
flag indicating the storage type, which would be set when the cache is filled.

With this approach on caching we should be able to eliminate a lot of slow code 
in relation to NSNumber 
creation/destruction/comparison as well as intermediate allocation of objects, 
and memory. From my 
observations on the times the current code takes to do things, I estimate that 
we should be able to get 
plotting times in the neighborhood of 0.100 or less.

Someone willing to do it?

Joan.

Original comment by carbon...@gmail.com on 27 May 2010 at 11:10

GoogleCodeExporter commented 9 years ago
There is a branch called BWNumericData that started to implement some of those 
ideas. I started to bring it up to 
date a little while ago but got sidetracked. You're welcome to take a look at 
that and see if those additions are on 
the right track.

Original comment by eskr...@mac.com on 28 May 2010 at 12:46

GoogleCodeExporter commented 9 years ago
I don't understand what the BWNumericData branch is all about. Seems way 
overkill for my purpose, which is 
simply bringing real time performance to core-plot. I will try to propose 
myself some simpler enhancements. BTW, It is still hard for me to get why 
NSDecimalNumbers are used at all. Would you point me to an application 
where 38 decimal digit precision is really needed for plotting a graph (?). 
Looks to me that double precision 
numbers should be more that enough for most applications. Thanks. 

Original comment by carbon...@gmail.com on 28 May 2010 at 10:45

GoogleCodeExporter commented 9 years ago
I agree that doubles are adequate most of the time. Core Plot was created with 
scientific and financial 
applications in mind where the additional precision is required to avoid 
rounding errors and to support very 
small and very large values.

Original comment by eskr...@mac.com on 28 May 2010 at 11:17

GoogleCodeExporter commented 9 years ago
The collapseLayers option allows for non-layer drawing now.

Original comment by drewmcco...@mac.com on 8 Jan 2011 at 9:57