Skip to content
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

Deeply nested widget trees with simple closure invocations have a high performance penalty #94585

Open
dnfield opened this issue Dec 3, 2021 · 7 comments
Labels
c: performance Relates to speed or footprint issues (see "perf:" labels) customer: money (g3) framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list perf: speed Performance issues related to (mostly rendering) speed team-framework Owned by Framework team triaged-framework Triaged by Framework team

Comments

@dnfield
Copy link
Contributor

dnfield commented Dec 3, 2021

Internal: b/292548521

Sample app:

import 'package:flutter/material.dart';

void main() {
  debugProfileBuildsEnabled = true;
  runApp(
    MaterialApp(
      routes: {
        '/two': (context) {
          return Material(child: Builder(builder: _getNestedBuilders(250)));
        }
      },
      home: Scaffold(
        body: Builder(builder: _getNestedBuilders(300)),
        floatingActionButton: Builder(
          builder: (context) {
            return FloatingActionButton(
              onPressed: () {
                Navigator.of(context).pushNamed('/two');
              },
            );
          },
        ),
      ),
    ),
  );
}

WidgetBuilder _getNestedBuilders(int count) {
  if (count == 0) {
    return (context) {
      return Container(
        width: 100,
        height: 100,
        color: Colors.red,
        child: IconButton(
          icon: const Icon(Icons.arrow_back_sharp),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      );
    };
  }
  return (context) => Builder(builder: _getNestedBuilders(count - 1));
}

A recorded timeline of this app on an Android Go device:
nested_builders.json.txt

This more-or-less mimics (and exaggerates) a common pattern in larger apps, where building the whole route can trigger a deeply nested widget heirarchy many of which are just Builder (or effectively Builder) widgets.

Initial build times on these routes on an Android Go device takes several milliseconds (compared to a fraction of a millisecond with much lower values in the _getNestedBuilders calls, or even compared to subsequent builds where they are not all marked as dirty).

I tried the following Dart program thinking it might replicate this issue, but it seems to just inline the calls:

import 'dart:developer';

class BuildContext {}

class Widget {}

typedef WidgetBuilder = Widget Function(BuildContext);

class Builder extends Widget {
  Builder({required this.builder});

  final WidgetBuilder builder;

  Widget _doBuild(BuildContext context) {
    try {
      return builder(context);
    } catch (e) {
      return Widget();
    }
  }

  Widget build(BuildContext context) {
    // Seems to defeat inlining, although I have not been able to replicate that
    // with simpler try catches.
    // return Timeline.timeSync('builder callback', () => builder(context));

    // Does not defeat inlining.
    // return _doBuild(context);

    // Seems to just inline and is fast.
    return builder(context);
  }
}

void main() {
  Timeline.timeSync('a', () {
    Builder(builder: (_) {
      print('hello, world!');
      return Widget();
    }).build(BuildContext());
  });

  Timeline.timeSync('b', () {
    Builder(builder: _getNestedBuilders(300)).build(BuildContext());
  });
}

WidgetBuilder _getNestedBuilders(int count) {
  if (count == 0) {
    return (_) {
      print('goodbye, world!');
      return Widget();
    };
  }
  return (_) => Builder(builder: _getNestedBuilders(count - 1)).build(_);
}
@dnfield dnfield added framework flutter/packages/flutter repository. See also f: labels. c: performance Relates to speed or footprint issues (see "perf:" labels) dependency: dart Dart team may need to help us customer: money (g3) perf: speed Performance issues related to (mostly rendering) speed P2 Important issues not at the top of the work list labels Dec 3, 2021
@goderbauer
Copy link
Member

Does Builder vs. Container make a difference?

@dnfield
Copy link
Contributor Author

dnfield commented Dec 3, 2021

Container is about as slow for the initial build.

@dnfield
Copy link
Contributor Author

dnfield commented Dec 3, 2021

From offline discussion: don't think this is related to inlining at all. It just seems like it's expense from inflating/mounting elements, but still not entirely clear why. Adding tracing for this is hard because it gets noisy.

@dnfield dnfield removed the dependency: dart Dart team may need to help us label Dec 3, 2021
@dnfield
Copy link
Contributor Author

dnfield commented Dec 3, 2021

It seems like each widget build gets a self time around 20us. 300 of them gets you to about 6ms of overhead. If any of these do more real work it gets worse.

@xster
Copy link
Member

xster commented Dec 3, 2021

Could they just be the cost debugProfileBuildsEnabled? e.g. have you AB tested with and without debugProfileBuildsEnabled?

@dnfield
Copy link
Contributor Author

dnfield commented Dec 3, 2021

Yes - also some tests where we eliminate runtimeType.toString from those traces.

It's likely that the actual work is a little less than 20us per widget, but when you have hundreds of them it adds up to ms of frame budget.

@jonahwilliams
Copy link
Member

FWIW when I looked at this the profiles were dominated by the timeline. If you go through and remove all the timeline calls in framework.dart (not necessary for a CPU profile) you might find the performance to be significantly different - it was on a Pixel 4 at least, though I don't have access to an Android Go device to verify.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: performance Relates to speed or footprint issues (see "perf:" labels) customer: money (g3) framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list perf: speed Performance issues related to (mostly rendering) speed team-framework Owned by Framework team triaged-framework Triaged by Framework team
Projects
None yet
Development

No branches or pull requests

6 participants