Skip to content

Add an experimental "no-copy" renderer#741

Merged
Adlai-Holler merged 9 commits into
masterfrom
AHNoBitmapCopy
Jan 14, 2018
Merged

Add an experimental "no-copy" renderer#741
Adlai-Holler merged 9 commits into
masterfrom
AHNoBitmapCopy

Conversation

@Adlai-Holler

@Adlai-Holler Adlai-Holler commented Jan 13, 2018

Copy link
Copy Markdown
Member

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 calls CGContextClearRect 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 uses mmap (and we use calloc which uses mmap too).

The API is virtually the same as UIGraphics functions – ASGraphicsBeginImageContextWithOptions and ASGraphicsGetImageAndEndCurrentContext.

You call ASEnableNoCopyRendering() to enable this. If you call it after rendering has started, it asserts, returns NO, 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.

screen shot 2018-01-12 at 8 06 38 pm

screen shot 2018-01-12 at 8 06 16 pm

screen shot 2018-01-12 at 8 06 47 pm

screen shot 2018-01-12 at 8 06 52 pm

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

@appleguy appleguy left a comment

Copy link
Copy Markdown
Member

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.

@Adlai-Holler
Adlai-Holler merged commit 1d105c2 into master Jan 14, 2018
@Adlai-Holler
Adlai-Holler deleted the AHNoBitmapCopy branch January 14, 2018 03:19
@nguyenhuy

Copy link
Copy Markdown
Member

💯 ❤️

bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants