Skip to content

Prohibitively high cost of "late" fields and variables #43361

Closed
@matanlurey

Description

@matanlurey

First off, I totally recognize late right now is being (in most cases?) directly lowered by the CFE, and not optimized 👍 . I'd probably still recommend using late where it makes the code significantly cleaner, but otherwise I don't quite understand why it's not just trading an NPE on (!.) for an equally cryptic LateInitializeErrorImpl and a lot of runtime + download cost.

I evaluated using late in two places today when migrating code, and decided against it in both cases:

Instance-level final field

-// @dart=2.9
+
class NgZone {
  final _outerZone = Zone.current;
- Zone _innerZone;
+ late final Zone _innerZone;
  NgZone() {
    _innerZone = _createInnerZone(_outerZone);
  }

  // ... Additional methods that access _innerZone ...
}

... and a null-check with an inlined error handler is emitted on every access point:

accessInnerZone$0: function() {
  var t1 = this.__NgZone_innerZone;
  if (t1 == null)
    t1 = H.throwExpression(H.LateInitializationErrorImpl$("Field '_innerZone' has not been initialized."));
  t1.useInstanceMethod$0();
}

Using a nullable type and a ! (and -o3) was much more terse:

-// @dart=2.9
+
class NgZone {
  final _outerZone = Zone.current;
- Zone _innerZone;
+ Zone? _innerZone;
  NgZone() {
    _innerZone = _createInnerZone(_outerZone);
  }

  // ... Additional methods that access _innerZone ...
}
accessInnerZone$0: function() {
  this.__NgZone_innerZone.useInstanceMethod$0();
}

Local variable

void funcWithNullableLocal(List<Foo> queue) {
- Foo foo;
+ late final Foo foo;
  void onCallback() {
    queue.remove(foo);
  }
  foo = Foo(onCallback);
  queue.add(foo);
}
funcWithLateFinalLocal: function(queue) {
  var t2, t1 = {};
  t1.foo = null;
  t2 = new R.funcWithLateFinalLocal__foo_get(t1);
  new R.funcWithLateFinalLocal__foo_set(t1).call$1(new R.Foo(new R.funcWithLateFinalLocal_onCallback(queue, t2)));
  queue.push(t2.call$0());
},
R.funcWithLateFinalLocal__foo_set.prototype = {
  call$1: function(t1) {
    var t2 = this._box_0;
    if (t2.foo == null)
      return t2.foo = t1;
    else
      throw H.wrapException(H.LateInitializationErrorImpl$("Local 'foo' has already been initialized."));
  }
};
R.funcWithLateFinalLocal__foo_get.prototype = {
  call$0: function() {
    var t1 = this._box_0.foo;
    return t1 == null ? H.throwExpression(H.LateInitializationErrorImpl$("Local 'foo' has not been initialized.")) : t1;
  }
};
R.funcWithLateFinalLocal_onCallback.prototype = {
  call$0: function() {
    C.JSArray_methods.remove$1(this.queue, this._foo_get.call$0());
  }
};

... compared with nullable Foo:

void funcWithNullableLocal(List<Foo> queue) {
- Foo foo;
+ Foo? foo;
  void onCallback() {
    queue.remove(foo);
  }
  foo = Foo(onCallback);
  queue.add(foo);
}
funcWithNullableLocal: function(queue) {
  var foo, t1 = {};
  t1.foo = null;
  foo = new R.Foo(new R.funcWithNullableLocal_onCallback(t1, queue));
  t1.foo = foo;
  queue.push(foo);
},
funcWithNullableLocal_onCallback: function funcWithNullableLocal_onCallback(t0, t1) {
  this._box_0 = t0;
  this.queue = t1;
},

/cc @leafpetersen @rakudrama - I can connect with you internally this week to see how I can help further!

Metadata

Metadata

Labels

NNBDIssues related to NNBD ReleaseP2A bug or feature request we're likely to work onarea-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.customer-google3dart2js-optimizationtype-performanceIssue relates to performance or code sizeweb-dart2js

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions