-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add an experimental "no-copy" renderer #741
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is historic! The only comparable improvement I recall seeing to the display system was the custom thread pool.
So the interesting thing is, I had gotten direct information from Apple that the UIGraphics API is supposed to be Copy-on-Write — specifically that the memory would only be copied if another write occurred after an image was acquired. Maybe they dropped that feature due to issues with it? No idea, but let's get rid of that ambiguity.
💯 ❤️ |
* Add "ASGraphicsContext" to skip copying our rendered images * Zero the buffer before making a context * Update license header * Update dangerfile * Make it a runtime flag * Restore GState for good measure * Free buffer if end without image * Enable the experiment, and cut out the middle-man * Fix typo
Normally
UIGraphicsGetImage
makes a copy of the bitmap data. Since we are done with the context, we don't need to waste time and memory making a copy. We quickly rack up hundreds of megabytes of total allocations from our CGImages and even though they're reclaimed, it's inefficient. For uncached ASImageNode renders, this happens twice per draw (opportunity: once we generate the cached contents, return them to ASDK intact rather than drawing them.)The end result is that ASGraphicsGetImageAndEndCurrentContext is virtually free, while UIGraphicsGetImageFromCurrentImageContext and CGBitmapContextCreateImage are two of the most time- and memory-intensive functions we call. It turns out that CALayer
_display
does basically the same thing.Another very very big win here is that,
UIGraphicsBeginImageContext
callsCGContextClearRect
right after creating the context, which is a huge waste of time with no benefit (hundreds of microseconds on an iPhone 6). The bitmap context's memory is already zero'd from the kernel since it usesmmap
(and we usecalloc
which usesmmap
too).The API is virtually the same as UIGraphics functions –
ASGraphicsBeginImageContextWithOptions
andASGraphicsGetImageAndEndCurrentContext
.You call
ASEnableNoCopyRendering()
to enable this. If you call it after rendering has started, it asserts, returnsNO
, and it stays turned off.Details are in
ASGraphicsContext.h
We achieve the vertical flip and HiDPI scaling by modifying the context's CTM. UIGraphics uses a private function
CGContextSetBaseCTM
. The internet and docs aren't don't say what makes the base CTM so special, but it seems to be working fine. One radar said it was used for shadowing but shadowing appears to to work. We'll keep an eye out.Profiling results with ASDKgram on an iPhone 6. I launched, waited for it to settle, then flung down once (about 2 screenfuls' worth).
Total memory allocated during display node rendering went from 60MB to 42MB – a 30% reduction.
Runs 1 and 2 are with the feature on. Runs 3 and 4 are with it off. If you look at runs 3 and 4, you can see how, at both levels (the ASDisplayNode render and the ASImageNode contents render) we have 2 equally-sized allocations in the "begin" and "end" functions. In runs 1 and 2 there's just the allocation at the beginning.
Here's a bit of rendering a text node pre-experiment with the removed bits marked:
![screen shot 2018-01-13 at 11 46 51 am](https://user-images.githubusercontent.com/2466893/34909591-1ca1e5da-f859-11e7-9de1-30698ec9c55e.png)