Skip to content

Avoid wrapping List<int>/Stream<List<int>> response bodies in a CastList/CastStream #178

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

Merged
merged 8 commits into from
Apr 23, 2021

Conversation

jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Apr 23, 2021

(Allows flutter/flutter#81010 to be fixed, though we need to change some code on the flutter side too)

TLDR: when writing data to a socket in dart, anything besides a Uint8List or Int8List will be necessarily copied (c.f. https://github.com/dart-lang/sdk/blob/3f0ad61daaac68e591bf01d0a3096926c30c1d42/sdk/lib/io/common.dart#L92)

shelf will unnecessarily wrap all Lists in a CastList which will perform runtime checks for every single element.

The combination of these behaviors causes requests for large files in the flutter run web server to be incredibly slow, observed to be on the order of multiple minutes locally. With the combination of this change and a patch to the tool to avoid creating Uint8List views, the time to serve the app drops to a few seconds.

I took a CPU profile starting from when chrome opened until the app's first frame. I could upload the actual timeline data too, but a picture something something a thousand words

Before:

Dart DevTools - Google Chrome 4_22_2021 5_50_04 PM

After:

Dart DevTools - Google Chrome 4_22_2021 5_50_52 PM

@google-cla google-cla bot added the cla: yes label Apr 23, 2021
@@ -52,6 +54,13 @@ class Body {
contentLength = encoded.length;
stream = Stream.fromIterable([encoded]);
}
} else if (body is Uint8List) {
Copy link
Member

Choose a reason for hiding this comment

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

Or could this just be is List<int>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yeah, changed!

Copy link
Member

@grouma grouma left a comment

Choose a reason for hiding this comment

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

Let's update the changelog and pubspec to prep for release.

@jonahwilliams
Copy link
Contributor Author

1.1.1 ?

Copy link
Member

@natebosch natebosch left a comment

Choose a reason for hiding this comment

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

Can you add a CHANGELOG entry and bump the patch version?

@jonahwilliams jonahwilliams changed the title Avoid wrapping typed data in a CastList or copying Avoid wrapping List<int>response bodies in a CastList Apr 23, 2021
} else if (body is List) {
contentLength = body.length;
stream = Stream.fromIterable([body.cast()]);
stream = Stream.value(body.cast());
} else if (body is Stream) {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder, while we are here should we assume that Stream.cast is likely to have similar overhead?

Suggested change
} else if (body is Stream) {
} else if (body is Stream<int>) {
stream = body;
} else if (body is Stream) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely, seems worth the optimization

expect(
await response.read().single,
isA<List<int>>()
.having((List<int> values) => values, 'values', [1, 2, 3, 4]));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.having((List<int> values) => values, 'values', [1, 2, 3, 4]));
.having((values) => values, 'values', [1, 2, 3, 4]));

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

@jonahwilliams jonahwilliams changed the title Avoid wrapping List<int>response bodies in a CastList Avoid wrapping List<int>/Stream<List<int>> response bodies in a CastList/CastStream Apr 23, 2021
@jonahwilliams
Copy link
Contributor Author

I don't have any merge permissions btw 😄

@grouma grouma merged commit 4b9294e into dart-lang:master Apr 23, 2021
} else if (body is List) {
contentLength = body.length;
stream = Stream.fromIterable([body.cast()]);
stream = Stream.value(body.cast());
Copy link
Member

Choose a reason for hiding this comment

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

If this is likely to be iterated over multiple times, you may be better to eagerly copy the list with List.from instead of using cast.

Copy link
Member

Choose a reason for hiding this comment

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

My hunch is this isn't a problem. I don't expect multiple iterations in most cases. Usually when we would need this cast it would be a Response which will be passed to dart:io which I expect iterates once. Otherwise, when this stream is getting passed to user code (where it may be iterated more than once) I'd expect the optimizations in this PR should kick in since it will most often be a Stream<List<int>> coming from dart:io to start with.

@natebosch
Copy link
Member

This is published

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants