Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Hint freed #19842

Merged
merged 23 commits into from
Aug 19, 2020
Merged

Hint freed #19842

merged 23 commits into from
Aug 19, 2020

Conversation

dnfield
Copy link
Contributor

@dnfield dnfield commented Jul 17, 2020

Description

Uses the new Dart_HintFreed API to notify the VM when the engine is done with a layer tree, and that a collection could potentially free memory.

This assumes https://dart-review.googlesource.com/c/sdk/+/150503 will land.

/cc @a-siva @rmacnak-google @chinmaygarde

Related Issues

flutter/flutter#56482

Tests

I added the following tests:

Unit tests that PictureLayer reports what it should
Unit test that showing an image, showing a new frame, and then notifying idle (which normally would happen in a "regular app" as frames are rendered) releases the SkImage/SkPicture.

Breaking Change

Did any tests fail when you ran them? Please read handling breaking changes.

  • No, no existing tests failed, so this is not a breaking change.

@dnfield dnfield marked this pull request as ready for review August 14, 2020 02:12
}

void _captureImageAndPicture(Image image, Picture picture) native 'CaptureImageAndPicture';
Future<void> _done() native 'Done';
void _encodeImage(Image i, int format, void Function(Uint8List result))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd leave these adjacent to the Dart code above that calls them.

(Even better would be to split these different fixtures into separate files, but this PR is probably big enough already)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, bad merge. Fixed.

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// FLUTTER_NOLINT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we add new files, we should have the lints enabled.

}

TEST_F(ShellTest, ImageReleasedAfterFrame) {
fml::AutoResetWaitableEvent message_latch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be getting a lint for the lifetime of this? If so you can make it a global or (I think) a field of a new subclass of ShellTest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are very smart.

@@ -33,11 +37,22 @@ class LayerTreeTest : public CanvasTest {
CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); }
const SkMatrix& root_transform() { return root_transform_; }

void OnCompositorEndFrame(size_t freed_hint) override {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// |CompositorContext::Delegate|

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and done

last_freed_hint_ = freed_hint;
}

fml::Milliseconds GetFrameBudget() override {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -528,10 +529,14 @@ class Shell final : public PlatformView::Delegate,
void OnFrameRasterized(const FrameTiming&) override;

// |Rasterizer::Delegate|
fml::Milliseconds GetFrameBudget() override;
fml::TimePoint GetLatestFrameTargetTime() const override;

// |Rasterizer::Delegate|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this stale?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure which line you're referring to - I moved this member up from the Rasterizer delegate to the CompositorContext delegate (Which the rasterizer delegate is now a subclass of).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. I was just confused by the two // |...| comments, and thought one of them might be extra.

std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
if (!root_isolate) {
return false;
}

tonic::DartState::Scope scope(root_isolate);

Dart_HintFreed(freed_hint);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the ordering of these two calls is important, then a comment here explaining that will probably be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@zanderso
Copy link
Member

It looks like the new test is failing in the presubmit checks.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 14, 2020

I fixed up a merge cconflict, but I'm wondering if this test might be flaky. I trie drunning it 50 times locally and it did not fail, but it did fail on CI once.

final Scene scene = builder.build();
window.render(scene);
scene.dispose();
window.onBeginFrame = (Duration duration) => _done();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should _done go on window.onDrawFrame. The docs say that that will flush the microtask queue before running it, which may reduce flakiness?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like that worked - just had to put it in onDrawFrame on the next frame.

Copy link
Member

@zanderso zanderso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm but this could probably also use a review from @chinmaygarde

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The access to the engine in the shell delegate callback is unsafe. Also, I am still trying to convince myself that there is no data race or race condition in test (I am not sure yet but will keep trying to convince myself)

@@ -15,11 +15,15 @@
namespace flutter {
namespace testing {

class LayerTreeTest : public CanvasTest {
class CompositorDelegate : public CompositorContext::Delegate {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave this in? It is not used and this class cannot be instantiated anyway since it is virtual. You may have wanted to use this in the fixture but instead opted to subclass it directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, will delete.

};

TEST_F(ImageReleaseTest, ImageReleasedAfterFrame) {
auto nativeCaptureImageAndPicture = [&](Dart_NativeArguments args) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor nit: We don't use camelCase for locals.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


AddNativeCallback("CaptureImageAndPicture",
CREATE_NATIVE_ENTRY(nativeCaptureImageAndPicture));
AddNativeCallback("Done", CREATE_NATIVE_ENTRY(nativeDone));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just "Done" is a bit too terse IMO. How about onBeginFrameDone since that is when you are making the call in the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done ;)

@@ -1205,6 +1205,12 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) {
}
}

void Shell::OnCompositorEndFrame(size_t freed_hint) {
if (engine_) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell APIs are only safely accessible on the platform task runner. The engine OTOH can only be accessed on the UI task runner. I believe you are on the raster task runner here. So this this API is not resilient to death of the shell, accesses the engine in a thread unsafe manner and is a non-standard shell method to boot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its fine for some methods to have different threading restrictions. But when they veer from convention, please document the same and also add an FML_DCHECK. In this case though, it just not thread-safe so lets fix that first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I want the shell to be the delegate here. This was originally living on the raster task runner.

I should be ok as long as I just jump to the UI task runner for this block with a weak ptr to the engine right?

Do I need to get the weak ptr on the platform task runner?

public:
/// Called at the end of a frame with approximately how many bytes mightbe
/// freed if a GC ran now.
virtual void OnCompositorEndFrame(size_t freed_hint) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document the threading restrictions that implementors must adhere to. I believe the shell is not thread-unsafe. Details below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what thread restrictions to document here. This is just called when there's a hint available for bytes freed - shouldn't it be up to the implementer what threads it uses when processing that information?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant whoever implements the delegate protocol will receive events on the raster thread. This might be unexpected to the implementer.

message_latch.Wait();

ASSERT_TRUE(current_picture_);
ASSERT_TRUE(current_image_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to convince myself this is not a data race. The latch will be signalled when the frame has begun. However, the current image will be set as the frame is constructed. Not sure how this is working.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought was that it's not because Dart gives us to us in the capture call (while we're still latched), and then we unlatch when Dart calls onBeginFrameDone (by which point it has already let go of its references).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was thinking this was onBeginFrame instead of onBeginFrameDone. I think that makes sense now. Thanks.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 19, 2020

I've updated the test to avoid racing the garbage collector - I'm not sure this is quite the race @chinmaygarde was concerned about, but it should be less flaky now.

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is quite the race ....

No that was not an actual issue but a misreading of the way the test was setup. I clarified it. Thanks and my bad.

public:
/// Called at the end of a frame with approximately how many bytes mightbe
/// freed if a GC ran now.
virtual void OnCompositorEndFrame(size_t freed_hint) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant whoever implements the delegate protocol will receive events on the raster thread. This might be unexpected to the implementer.

message_latch.Wait();

ASSERT_TRUE(current_picture_);
ASSERT_TRUE(current_image_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was thinking this was onBeginFrame instead of onBeginFrameDone. I think that makes sense now. Thanks.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 19, 2020

I'm removing the test - it's flaky, and efforts to make it not be flaky are making it pass without these changes. I don't think it's accurately representing the state of the framework, and it really wants to be a benchmark about memory usage and collection.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 19, 2020

We still have coverage that OnCompositorFrameEnd is called with the right value(s) in the layer tree unittests.

@dnfield dnfield added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Aug 19, 2020
@dnfield dnfield merged commit 3930ac1 into flutter:master Aug 19, 2020
@dnfield dnfield deleted the hint_freed branch August 19, 2020 21:04
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 19, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 20, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 20, 2020
gspencergoog pushed a commit to gspencergoog/engine that referenced this pull request Aug 20, 2020
* Hint the VM when a layer or picture goes out of scope
std::unique_ptr<RasterCacheResult> image;
};

template <class Cache>
static void SweepOneCacheAfterFrame(Cache& cache) {
static size_t SweepOneCacheAfterFrame(Cache& cache) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to document what size is returned from this function. Currently, it seems to only return the layer external size, and one might ask why not also include the image size of the raster cache. Is it because we only want to report the memory size that's managed by Dart VM so the SkImage memory size is irrelevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image size is included in the layer external size. The SkImage size is really of primary importance here.

However, we have to figure out how to get that out of what the Dart VM sees - @jason-simmons has a patch open to remove that, but we'd still want it here.

dnfield added a commit to dnfield/engine that referenced this pull request Aug 25, 2020
dnfield added a commit that referenced this pull request Aug 25, 2020
This caused over-aggressive GCs, which vastly increased CPU usage benchmarks.

* Revert "fix build (#20644)"

This reverts commit b59793e.

* Revert "Hint freed (#19842)"

This reverts commit 3930ac1.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants