-
Notifications
You must be signed in to change notification settings - Fork 27k
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
removing Image widgets does not reduce Graphics memory allocation #56482
Comments
This issue looks similar to #19558. The next steps of fixing this would also be similar:
|
@liyuqian Isn't this WAI? Even if the images are not referenced in the widget tree, they may be referenced in the image cache in the framework. Putting the application in the background clears the cache. If the application released all ui.Image references and we did not observe a GPU memory decrease, then that would be a problem. That would be the more accurate perf test. |
Totally fine to close this as WAI but it is surprising from developer's point of view so we might want to think about how to expose this in tooling to guide them. Image cache gets repeated many times in these conversations; perhaps it deserves its own metrics in the memory section of DevTools @terrylucas |
@chinmaygarde : if it's image cache that's retaining those images, and there's a way for developers to clear those image cache, then this is totally WAI. A reproduction app would clarify whether it's image cache WAI or some unexpected issues. |
There are definitely ways to manage the images in the cache and its limits.
This is true. Also, accessing the image caches can be implicit so users may not even be aware that this is whats going on. Also, the image caches are simple LRU caches with arbitrary limits that have no bearing on actual device capabilities. So it is totally possible for users to unknowingly use the image cache all the way to device memory exhaustion. From the engine's perspective, these caches are particularly frustrating because the references to the images in this cache are strong and there aren't too many opportunities to optimize textures in this cache. For example, if the images were to be speculatively evicted from device memory, rendering them again would lead to a massive increase in frame time because the host to device transfer would need to be carried out again. Also, making these images evictable from device memory means that the engine would need to maintain a host side copy of the allocation which would sharply increase heap memory usage. ui.Images were meant to be references to images that are extremely fast to render by referencing them in the widget tree. But they are also expensive in device memory. However, image caches keeps these references around for quite periods well beyond their intended use. In the past, we have done a lot of work in the engine to make it so that allocating and collecting these images is as fast/performant as possible. But till the application/framework holds a strong reference to these images, the issue of device memory usage will keep coming up. If a long term solution to the frequent discussions surrounding device memory usage of image caches is to be devised, it probably involves an alternative to image caches that maintains image references more closely tied to their use in widgets hierarchies. Till then, exposing the cost of images in the cache from tooling seems like a good idea. To answer the question: "removing Image widgets does not reduce Graphics memory allocation, why?", The TL;DR is widgets don't own the Graphics (GPU/Device) memory allocations for the images they display, strong references to ui.Images do. So not using a widget does not imply that the graphics memory will get collected.
Yes, this issue currently seems like asking a question instead of raising an issue. Lets prepare a reproduction case to ensure we aren't talking about a construction issue. |
@mehmetf : can you or the "customer: money" team please provide us a reproduction case so we can figure out if it's WAI or an issue of ours? |
Download a large PNG to your application assets and name it import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
void initState() {
imageCache.maximumSize = 0;
super.initState();
}
void _incrementCounter() {
if (_counter == 10) {
imageCache.clear();
}
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
Text('Images will go here $_counter'),
if (_counter > 2 && _counter <= 5)
Image.asset('assets/very_large.png'),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.ac_unit),
),
);
}
} I tried to set the image cache size to 0 and also clear it when the counter hits 10 but none of that helped get the graphics size back down to "normal" levels. In my experiment, before the image is shown graphics memory reported by adb is 55M. After image is shown it is 178M and it stays there no matter what. In fact, I misspoke earlier. After I put it the app to background and foreground again, the graphics memory goes down but not to initial levels. It stays at 140M. |
I'm going to take a quick look at this - I have a couple ideas about things to try here. |
Something strange here. The image cache is not holding a reference to this image for sure - it's not holding anything. Something is though. Trying to figure out what. |
By something is, I mean the Dart VM still seems to believe it's live, even though it seems like it shouldn't be. |
/cc @rmacnak-google |
With an I'd like to think it would get GC'd more quickly due to the size of the image I was working with (it was taking close to 200mb of memory by itself). It was taking several seconds and many many frames after it was gone before getting GC'd. I'm not sure if that's just WAI or if that's reasonable behavior. /cc @jason-simmons who was also looking at this |
Can you capture and attach a heap dump from the observatory for analysis here? |
@chinmaygarde - it's not showing up in the heap snapshots, but is showing up in the old generation of the allocation profile. Here are the CSV dumps. One from app startup, (1) after the image loads, (2) after the image has been disposed by the framework but you can still see the native memory in the old generation, and (3) after I press the button 20 or 30 more times which eventually triggers a GC and the old gen is cleaned up. Again, the main concern is that native memory is remaining resident for too long. In a real application, if I had to show that large image and then got rid of it, I need to be able to show more large images again without potentially OOMing in between because Dart has failed to GC the unreferenced memory. |
And in case it's not clear, the memory showing in the old generation is also visible in Xcode - e.g. the SkImage_lazy and its backing texture are still around in memory until the GC finally grabs it. |
Did you also disable the image cache (not sure if you are using Mehmets reduced test case)? I forget which variants consult the cache.
That is to be expected. As long as there is a reference to the object in the Dart VM (even if that is a garbage reference the collector hasn't bothered collecting yet), the engine cannot collect the texture from graphics memory.
Ok, this is good. It means that when a reference is finally collected, all native allocations including ones resident in graphics memory are collected.
Have you actually seen this happening? Before growing the old space, the VM will attempt to release garbage references. Attempting to allocate more large objects will ensure that the old garbage references are released first. Can you setup an example where one large image is loaded after another (make sure the old one is actually collectable otherwise you'll just run out of memory)?
Without manual memory management, you are never going to be in complete control of when something gets collected. Some things you can try though:
Only the second suggestion will help when not going into the background. But then again, the collector taking a while to actually collect references seems like expected behavior. After attempting that a reduced test case where very large images are loaded, displayed and unloaded one after another runs fine, I think we can safe say that the answer to the question "removing Image widgets does not reduce Graphics memory allocation, why?" is that the image is actually referenced in a cache or is awaiting collection by the GC, and, no actual bug has been identified. |
I would have expected a call to |
This had to get bumped to september to accomodate relanding a new fix. Those patches are currently in progress and should land "soon". |
Engine changes have landed. #64582 is still under review. |
Framework side PR has been closed in favor of new engine change to create a disposable image wrapper. That is under review here: flutter/engine#21057 It is currently blocked by #66379 and blocked by general difficulty in keeping the tree open of late. |
engine patches have all landed, final framework patch is posted and under review. |
Framework patch is awaiting another engine roll before it passes CI, but is still available for review. I expect to land it this week, which may mean it slips into the first week of October. Leaving it in September for now. |
Here's a quick overview of the problem before this bug was closed:
Here's an overview of what had to happen to fix this, and some of the failed/reverted attempts.
This solution isn't perfect and has room for improvements:
|
This broke CanvasKit and some internal targets. Reverting and reopening while I investigate a reland. |
Fix has relanded, I think fo rreal this time :) |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Internal: b/157467549
If the app was put to background and then resumed, the GPU memory went back down.
not sure what the right approach for this. Changing the widget tree and not seeing an expected memory behavior is hard to explain. Is this a bug? Is it something that should be in documentation?
/cc @cbracken @mehmetf
The text was updated successfully, but these errors were encountered: