Skip to content

[dart2js] Optimize Flutter .runtimeType patterns #41761

@rakudrama

Description

@rakudrama

b328cad Added a benchmark for the canUpdate hot method:

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType &&
        oldWidget.key == newWidget.key;
  }

While the new Rti is quite a bit faster that the old rti, and faster than Dart JIT VM, the benchmark on dart2js on V8 still lags AOT VM by 3-4x.

The Type instance is cached on the _Rti object.

There are several things we can do:

  • Make use of the context's interceptor rather than do the type dispatch again. This change is a first cut at this - defining slightly different runtimeType getters for intercepted types, closures, arrays and regular Dart objects.
  • The canUpdate code uses a one-shot interceptor, but only because the parameter type is nullable.
    • Specializing the interceptor on the call site receiver type would speed up the interceptor dispatch.
    • However, with strong NNBD checking, null becomes impossible.

There are many == methods with the following pattern:

class ValueKey<T> extends LocalKey {
  const ValueKey(this.value);
  final T value;
  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) return false;
    return other is ValueKey<T> && other.value == value;
  }

class CircleBorder extends ShapeBorder {
  ...
  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is CircleBorder
        && other.side == side;
  }
  • runtimeType is implicit this.runtimeType.
  • The class is often not generic.
  • The 'is' test is redundant with the runtimeType test.

Ideas:

  • If no subclass of the class declaring == has type parameters, and no class overrides runtimeType, other.runtimeType != this.runtimeType can be reduced to JavaScript other.constructor !== this.constructor.
  • other can be refined similar to an is-test: other is known to to be a subtype of the declaring class, which often will allow the following 'is'-test to be eliminated.
  • Even with a class hierarchy, the valid receiver type might be constrained to be exact (e.g. the method is shadowed in all live subclasses and not called via a super-call).
  • If the declaring class is a non-generic leaf in the instantiated classes hierarchy, other.runtimeType == this.runtimeType is equivalent to an is-test, e.g. other is CircleBorder.

The classes that override get:runtimeType are an impediment to the above ideas, especially for the argument other of == which could be anything.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions