-
-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance Enhancements #29
Comments
The original performance bottleneck in the Android version was many extra allocations (of arrays) inside the rendering code. Memory should not be allocated inside rendering code. So @PhilJay has moved that code into a Buffer that is preallocated, and delegating the rendering calculations to the Buffer classes while rendering. I chose to not move the rendering calculations to the Buffer classes, but just preallocate the required memory the same way - but have the rendering calculations in the same loop as the rendering code. This way it is far easier to manage the code. Also it IS a performance gain that the rendering calculations are not being done inside extra functions, and the data is looped only once inside the rendering code instead of twice. Btw, in Swift performance is FAR FAR better than Java anyway, so you would have seen that performance gain even without pre-allocating those arrays. In order to graphically explain it, this is what is/was being done in the rendering code:
Also note that in Java, abstractions (functions, classes, inheritance) come with a great price. It is not for nothing the Google themselves wrote an important recommendation:
Summing it up:
|
Thanks for your explanations Daniel. I'm not quite sure what to take from this, though. override func viewDidLoad() {
|
Well I haven't tested all of the charts for performance yet - so I need to do that soon. |
Thanks a lot Daniel. Looking forward to what you can squeeze out of this. Would be awesome to have a viable companion for MPAndroidCharts on iOS. |
Well, I've played a little with the LineChartRenderer (which is particularly slow compared to for example the BarChartRenderer) and Instruments says that the CGContext's drawPath is the culprit here. |
I'm testing this right now, and yes it seems that the actual |
Yes, Core Graphics definitely is rendering the paths slow. It seems to happen because of the resolution - the resolution on newer Apple devices are very high, which means more pixels to render. I think the CG is rendering on the CPU instead of on the GPU. Maybe someone knows a way to change that? |
CG is most definitely using the CPU for rendering and as far as I know there's no way to change that. |
It's actually the devices' CPUs that are slow. In most cases a very slow
CPU gives far better experience with iOS than a fast CPU with an Android.
But Apple didn't think about the cases where an optimal OS is not
sufficient to keep it fast.
I guess that when we use lot's of points we can just avoid animations - But
I'm still hoping to finding a way of improving the performance for the Line
based charts.
It seems like specifically the Path drawing system is slow, and when
playing with Miter and Flatness, and disabling dashed lines and
Antialiasing - the performance gets much better for 500-700 points on an
iPhone 6, but still Jerky with a 1000
|
There might be a chance with using the UIBezierPath. As I said, that thing is sufficiently fast as soon as you don't call 'stroke', which essentially tells the system that the path is a stroke and shouldn't be filled from the startpoint to the endpoint. I have no idea what is going on with that in the background, but this shows that the graph drawing itself isn't the issue by itself. However, since I couldn't find a way to use the UIBezierPath properly for the line chart, it might be a viable candiate for the grid lines as one could trick the path to draw without calling stroke. Even though that is 'wrong' from an API point of view, it should yield better performance. |
The UIBezierPath is just a UIKit wrapper around CGPath, CGContextDrawPath, Actually there's absolutely no reason to use it when you know how to use And yes, we do need to call "stroke", as we do want to stroke that path... On Fri, Apr 17, 2015 at 10:50 AM, AlBirdie [email protected] wrote:
|
@danielgindi have you thought about that if the animation is slowing things down, how about draw all lines without animation, and use a mask view covering it, then animate the mask view to imitate the animation? |
Well that's a great idea! :) Although there's a downside: My list of things to try are:
If there are other ideas I'd love to hear them! |
Daniel, did you try offloading work to the GPU yet? |
Trying a few different techniques, I'll give you an update in a few days! On Mon, Apr 20, 2015 at 12:38 PM, AlBirdie [email protected] wrote:
|
@AlBirdie what kind of performance bottleneck you met? I am currently ownning a product that draw charts just like ios-charts, we already had a chart library internally. I also concerns about the performance, currently, we just load 100-1000 data sets, seems ok now. I am also considering changing to ios-charts if possible in the future, but our library had gestures that might have conflicts with ios-charts. |
The performance trouble is a slow frame-rate in animations when having to Regarding Gestures - we are using standard UIGestureRecognizers - which you On Mon, Apr 20, 2015 at 12:53 PM, Xuan [email protected] wrote:
|
@danielgindi well, I think we use the mask view to overcome the animations... Our line chart has gradient layer. As your demo, your animation can do both X+Y direction at the same time, while we just do the X direction. I am not sure if the mask trick can help you. |
@liuxuan30 just general slow performance I guess. Animations aren't an issue since I'm not working with those. I'm working on finance charts where you need to have multiple datasets in a single chart (multiple stocks + a range of indicators). For a data range of 250 items that easily adds up to several thousand points that need to be rendered at once during panning and pinching. The commercial solution I'm currently working with does that pretty well (using OpenGL you can render thousands of points without overloading the CPU), but I'm not a friend of closed source libraries where you have to wait months for bug fixes. |
I see, finance data is disaster. Our server force to just send up to 1000 data sets to the mobile device, so reduced our overload. Is there any chance to use OpenGL for ios-charts? @danielgindi |
OpenGL support would be aces. A GPU rendered line chart would probably suffice for now. |
There's an improvement with CGContextStrokeLineSegments as opposed to using paths, so you should try this. |
Thanks Daniel, I'll create a small test app that benches the two version against each other to see what's what. What performance gains did you gain using the new LineSegments? |
@AlBirdie do you feel the difference with line segments? Also I've just realized: In android to get the maximum performance you set it to hardware layer- in which case the dashed lines aren't dashed, they're all solid. So first thing if you disable dashed lines on iOS you also get a significant boost! But I think I can actually draw it using a OpenGL ES on CIImage, but need to be very careful because if one GL line runs when app is inactive- it will crash. Also there's a chance I can still allow dashes by pre-rendering a texture. It's gonna take some work and it's not my main priority, but I'm starting a side project of a OpenGL layer that can seamlessly replace the CGContext. |
For years in my day job we've used a commercial product that as noted above by @AlBirdie makes extensive use of OpenGL, and thus bypasses CoreGraphics for everything except annotations. Certainly they seem to be able to cope with pretty large datasets with fairly smooth rendering and they claim to use the GPU for this. However their approach comes with massive headaches, not least bugs they seemingly cannot be bothered to fix such as async rendering calls that crash in OpenGL as the app has gone into the background, idiotic class structures and insane restrictions on customisability. What I want doesn't exist today, but in iOS-Charts I see that it might be the way to get there. Anyway, for me reliability wins out over performance every time, but it's by the tiniest margin, both are massively important. |
ps in my opinion dashed lines are a hangover from the days when computers didn't have the option to render in colour or even grayscale - they are not needed unless we're rendering to pure black and white and making something that looks like an old letterpress-printed textbook, surely? ;) So it's far less important to optimise for those than it is to optimise for the general 10K points case. |
Absolutely with you @onlyforart regarding the dashed lines. |
@AlBirdie yes that's the one. Don't even mention the crosshairs. Made with the help of too much Newcastle Brown Ale and undoubtedly aimed at the corporate market (where a gnarly API is actually a positive sales factor, because it locks customers into an expensive support/maintenance cycle). Did you know the history of those guys? actually (in their deep past) they were real pioneers in the software industry. Anyway it's time to move on and look to the future now.
|
LOL @onlyforart , you had me at Newcastle Brown Ale. 👍 :) |
@onlyforart , @AlBirdie thanks for your insights :-) It's really nice to see people ditching commercial enterprise product for our library, although I feel bad for them... I'm conflicted! I was hesitant to let OpenGL have a slice of this because I knew there are possible crashes if it's not managed perfectly, and that's not really possible in a UIKit app. If you're using OpenGL to create a game, the whole thing is an OpenGL canvas and you do not have to worry about a UIKit call trying to cause an OpenGL rendering while in the background. @onlyforart your point about dashed lines is correct, but unfortunately I have had experience with clients demanding to have dashed lines. There was a case on Android where to boost performance the developer had to change the layer to a hardware layer, and told the client that dashed lines will become solid lines, that's the cost. And there's of course an option to move all drawing code to OpenGL, but then drawing a single line is a headache, but you can create a texture for a dashed line and work with it. They didn't want to pay for it so dashed lines were certainly not so critical, but they did make a a lot of noise about it. A note to myself: Try to use GLKit for the rendering. See what happens. And if I take your list:
|
@danielgindi , don't feel bad for them. If the product you're developing is basically a rotten tomato and yet you still charge some serious dough for it, people will eventually move to a different product. There's nothing wrong with that. I've been developing finance charts for several years now, have used pretty much all the solutions for iOS that are currently out there, and have in fact written my own charting engine in ActionScript3 back in the days, so I feel pretty confident saying that iOSCharts and MPAndroidCharts are in fact the only products I can currently recommend to any developer who needs charting in his app. Everything else out there just doesn't cut it. Regarding the second requirement, if you guys are going to implement this, it has to be optional. We've got this kind of consolidation in one of our charting products and customers are steadily moving to a fixed frequency instead. Changing frequencies during zooming turned out to be not only confusing to the average finance charts user, power users got annoyed because they wanted fixed frequencies. |
We've never imagined forcing this on the users! You can see that the code already includes the Filters, and in MPAndroidCharts you can see the historically the filters were enabled by a property that sets the filter (any custom filter or the built in ones), but removed later due to structural changes. When we implement it the functionality will stay the same - it will be just another cool feature :-) |
@AlBirdie Re "Regarding the second requirement, if you guys are going to implement this, it has to be optional. We've got this kind of consolidation in one of our charting products and customers are steadily moving to a fixed frequency instead. Changing frequencies during zooming turned out to be not only confusing to the average finance charts user, power users got annoyed because they wanted fixed frequencies." Completely agree - this is for charts for informational/non-trading users. Trading users have different needs. @danielgindi Re "abstracting the datasource" maybe this is something we could contribute to, if we have time. No promises, but I'll add it to our backlog. |
Did anybody play around with splitting the drawing code to separate threads (CALayer.drawsAsynchronously)? That might help in case this allows to draw chart extras, each dataset, the grid and axes separately. Given my very humble experience with CoreGraphics (read; none whatsoever ;-)), I haven't done any experiments with this, I just found it when I was looking for GLKit and how it could improve the chart performance. |
I needed to show a candle stick chart with 10k data points so I ran some time measurements on CandleStickChartRenderer.drawDataSet(). It turned out that most of the time is spent on calling dataSet.entryIndex (lines 76,77). I might be mistaken, but it looks like the dataSet.entryIndex() call is redundant as _minX, _maxX values are always equal to the minx, maxx returned from dataSet.entryIndex() I've managed to make a candle stick chart with 10k data points pan/zoom smoothly. by changing var minx = max(dataSet.entryIndex(entry: entryFrom, isEqual: true), 0); to var minx = max(_minX, 0); I've made the same change to LineChartRenderer and was able to show a combined candle stick/line chart with 2 data series (10k data points each) |
Wow, that's a massive performance increase @dorsoft ! I've just tested this and I couldn't believe my eyes. Even on an iPad 2 with 4 CombinedCharts showing up to three datasets each with 250 datapoints each and automatic min/max calculations of the y axis we are now able to pan and zoom all charts simultaneously in a fairly smooth manner. It is not 60fps, but close. Impressive for such an old device and WAY (!) faster than the commercial OpenGL solution we've talked about earlier. |
I guess I've got to try this :) And need to discuss with Phil also to
understand if there are any implications...
|
I just read this, looks very interesting but it's gonna need some intense testing to see if it is actually suitable for all scenarios :-) |
I've created a PR with candle chart performance enhancement. |
I've found a possible bottleneck. In a realtime chart Adding many entries per second to the chart I noticed that automatically the calcMinMax of the dataset is called just after EACH calcMinMax function determine the min and max values using .forEach that is causing the CPU over 60% spent looping the values, moreover in the main thread. I'm performing some experiment disabling min/max calculation or using native for loop instead of array .forEach @danielgindi please have a look, is really calcMinMax on all values needed? I calculate min/max Y values manually in my code and only on visible values, so maybe you could expose some boolean to enable/disable min/max auto calculation and improving performance removing the forEach function and avoid function calls.
|
@samueleperricone Please make this a separate ticket and I will prioritize this. |
I am loading more than 13000 records in line charts for iOS. But, charts freeze the UI while loading. Also, after loading If the user selects any point then also it takes too much time to highlight the selection. |
Are there any plans to enhance the performance of the library in a way Philip did with MPAndroidCharts?
With his enhancements it is now possible to render thousands of data points smoothly on Android.
I've had a quick glance at the ios-charts implementation and from what I've seen it is based on the original MPAndroidCharts implementation without the latest performance enhancements.
Having it render thousands of points as well would be HUGE plus for the library as it would effectively render all commercial versions useless (most of which are a waste of time and money anyways).
The text was updated successfully, but these errors were encountered: