-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[Breaking Change Request] Deprecate UnmodifiableUint8ListView
and friends
#53218
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
Comments
Does dart2js have similar performance issues with
Very happy to hear that we may be able to move forward with cl/262241 😄 |
The argument for introducing That use-case should still be supported, which means we need a way to create an unmodifiable view on an existing Also, when you extract the buffer of an Optimially, that might just be one bit of information stored on the That suggests another approach:
Then the native use-case for an external-unmodifiable-memory backed (The VM, which still has a non-view |
Yes for For the receiver type Less of a problem for
|
I am happy for now to make
After protecting against SIGSEGV, this might be the most important use-case - handing off a view of data that was assembled via mutation. I'm not sure how, without additional magic, to implement
|
I'm thinking that adding an instance method might be better than another factory constructor. abstract final class Uint8List implements List<int>, _TypedIntList {
external factory Uint8List(int length);
external factory Uint8List.fromList(List<int> elements);
/// Creates a [Uint8List] _view_ of the specified region in [buffer]....
factory Uint8List.view(ByteBuffer buffer,
[int offsetInBytes = 0, int? length]) ...
/// Creates a [Uint8List] view on a range of elements of [data]....
factory Uint8List.sublistView(TypedData data, [int start = 0, int? end]) { ... }
/// Option 1: another factory constructor with 'view' in the name.
external factory Uint8List.unmodifiableView(Uint8List list);
/// Option2: an instance method.
Uint8List asUnmodifiableView(); |
I can't find any usage in ACX. |
fine by me. fyi @yjbanov |
IIRC, the original request was from the assistant team. That's where we should look for existing uses. |
@vsmenon I am going to approve this change. Please remove the label if you see fit. |
lgtm |
This is the first of several steps to remove the unmodifiable views for typed data classes. The end goal is that dart2js has only one class implementing `Uint8List` so that `Uint8List` accesses can always be compiled down to JavaScript code that directly uses indexed property accesses (`a[i]`). This first step deprecates the unmodifiable view classes to help prevent their use in new code, and adds `asUnmodifiableView()` methods as a replacement for the small number of places that use the classes. The next steps (see #53785) are to remove uses of the unmodifiable view classes from the SDK. Once this is complete the classes themselves can be removed. TEST=ci Issue: #53218 Issue: #53785 Change-Id: I04d4feb0d9f1619e6eee65236e559f5e6adf2661 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/321922 Reviewed-by: Nicholas Shahan <nshahan@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Stephen Adams <sra@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com>
This reverts commit b657773. Reason for revert: blocking Dart SDK -> Engine roll (flutter/flutter#137054) Original change's description: > [typed_data] Deprecate UnmodifiableUint8ListView and friends > > This is the first of several steps to remove the unmodifiable views for typed data classes. The end goal is that dart2js has only one class implementing `Uint8List` so that `Uint8List` accesses can always be compiled down to JavaScript code that directly uses indexed property accesses (`a[i]`). > > This first step deprecates the unmodifiable view classes to help prevent their use in new code, and adds `asUnmodifiableView()` methods as a replacement for the small number of places that use the classes. > > The next steps (see #53785) are to remove uses of the unmodifiable view classes from the SDK. Once this is complete the classes themselves can be removed. > > TEST=ci > > Issue: #53218 > Issue: #53785 > > Change-Id: I04d4feb0d9f1619e6eee65236e559f5e6adf2661 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/321922 > Reviewed-by: Nicholas Shahan <nshahan@google.com> > Reviewed-by: Lasse Nielsen <lrn@google.com> > Commit-Queue: Stephen Adams <sra@google.com> > Reviewed-by: Alexander Markov <alexmarkov@google.com> > Reviewed-by: Martin Kustermann <kustermann@google.com> > Reviewed-by: Ömer Ağacan <omersa@google.com> Issue: #53218 Issue: #53785 Change-Id: I0bb042269f9ff8e5cd69619cf97b60c79ea98cbf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/331680 Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Derek Xu <derekx@google.com>
We might have a use case: Marking object graphs deeply immutable to be able to share them across isolates in an isolate group.
For now, I'll just omit support for unmodifiable typed datas in my prototype. |
We need a |
@gmpassos could you describe why you need |
@rakudrama thanks for the question. Note that I advocate for having two getters: For me, it's very odd to have a method that transforms X into Y but can't check if X is in the Y state. Here are two classes that demonstrate two basic usages for them: class ComputationCache {
final Map<String, Uint8List> _cachedEntries = {};
void put(String key, Uint8List bs) {
// The ideal implementation should throw an exception if the
// [bs] parameter is not unmodifiable, to ensure that it's
// used correctly and maintain the cache's integrity.
//
// if (!bs.isUnmodifiable) throw ArgumentError("Can't store a modifiable buffer");
// An `isUnmodifiableView` check could avoid a call to `asUnmodifiableView`:
_cachedEntries[key] = bs.asUnmodifiableView();
// NOTE: `asUnmodifiableView` lacks documentation on avoiding
// unnecessary instantiations and wrapping.
}
Uint8List? get(String key) {
var cached = _cachedEntries[key];
// Since there's no `Unmodifiable` or `UnmodifiableView` type,
// an assert should inform the rule and check it in tests:
//
// assert(cached == null || cached.isUnmodifiableView);
// Since it's only stored as an UnmodifiableView,
// it can be returned without a copy, ensuring cache integrity:
return cached;
}
}
class BuffersPool {
final List<Uint8List> _pool = [];
void add(Uint8List buffer) {
// The ideal implementation should throw an error if the
// [buffer] parameter is unmodifiable:
//
// if (buffer.isUnmodifiable) throw ArgumentError("Can't store an unmodifiable buffer in the pool.");
// It should also throw an error if it's an UnmodifiableView:
// if (buffer.isUnmodifiableView) throw ArgumentError("Can't store an UnmodifiableView of a buffer in the pool");
_pool.add(buffer);
}
Uint8List? get() {
if (_pool.isNotEmpty) {
return _pool.removeLast();
}
return null;
}
}
|
So, if |
However, this is insufficient for the second example, Also, how do you write tests to ensure that an implementation of an interface follows a contract where a returned value should be unmodifiable, without attempting to modify it and checking for an error? |
It is unfortunate that we can't have two interfaces, The breaking change is motivated by a >10x performance penalty for having two separate interfaces on some platforms (when compiling to JavaScript, it requires a very complicated dispatch to do Ordinary |
Well, I think that removing the |
Go to your tensor.dart (\AppData\Local\Pub\Cache\hosted\pub.dev\tflite_flutter-0.10.4\lib\src\tensor.dart) and change:
For:
|
To solve this problem, I did the following: |
this is somehow black magic and works. |
TL;DR: To ensure that typed data has reliable performance on the JavaScript platforms we need to get rid of the
UnmodifiableUint8ListView
and similar classes.What?
Deprecate
UnmodifiableUint8ListView
and everything else in unmodifiable_typed_data.dart.Replace the constructor for
UnmodifiableUint8ListView
with a new factory constructorUint8List.unmodifiableView(Uint8List list)
, and likewise for the other unmodifiable views.Why?
Uint8List
is sealed to prevent poor performance, but the SDK itself is constructed with the pattern that sealing is trying to prevent - multiple implementations with non-aligned 'shapes', forcing each byte access to be a dispatched aka virtual call. The remedy is to remove the user-visible classes that have this bad pattern.Benchmarking shows that the polymorphism costs ~10x performance on code that is capable of reading from a regular
Uint8List
and anUnmodifiableUit8ListView
. We could recover that performance by eliminating the polymorphism.(Comparing golem
Benchmarks?benchmark=TypedDataPoly#benchmarks%3DTypedDataPoly.A_UV.array.100%2CTypedDataPoly.A_V.array.100%3Btargets%3Ddart2js-production
)In JavaScript there is a single class that can support
Uint8List
efficiently:Uint8Array
. Having a separateUnmodifiableUint8ListView
class makes the operations polymorphic and too slow - often over an order of magnitude slower. Reliable performance of typed data requires all instances ofUint8List
to be implemented asUint8Array
.It is too difficult to provide a fiction that one implementation class (JavaScript
Uint8Array
) conditionally implements an interface. So we need to have a single interface type in the SDK.It might turn out that it is possible to use JavaScript subtyping by extending
Uint8Array
, but in the past we have found that ES6 and later features do not always work efficiently when used in their full generality. Preliminary experiments show that the problem we are trying to avoid with this breaking change request will re-assert itself at a lower level in at least one popular JavaScript engine. RemovingUnmodifiableUint8ListView
as a user-visible type gives us the freedom to choose the implementation strategy that works well across browsers, including possibly ignoring the checksImpact
There would be no impact on performance the native VM implementations. (The VM implementations have already been optimized to align the private classes that implement
UnmodifiableUint8ListView
with those that implement writableUint8List
s.)Current usage of the form
UnmodifiableUint8ListView(list)
becomesUint8List.unmodifiableView(list)
.The unmodifiable views are fairly special purpose and only lightly used so this update should not be a large burden and can be explained in the deprecation message.
What is lost is the ability to test for unmodifiability via
x is UnmodifiableUint8ListView
. I have not found examples of this. If it is necessary to test for modifiability then an affordance should be provided that is not based on type tests.How?
Replace the constructor for
UnmodifiableUint8ListView
with a new factory constructorUint8List.unmodifiableView(Uint8List list)
, and likewise for the other unmodifiable views. The initial implementation of the factory constructors calls the existing private implementation classes.Deprecate all the unmodifiable typed data view classes.
The JavaScript platforms can now change the unmodifiable variant to be a
Uint8Array
(possibly with a 'permission' tag, similar to how JavaScript'sArray
implements growable, fixed-length and constantList
s, or some other trick). This will beRemove the deprecated classes.
It might be nice to add
immutable:
optional arguments to existing constructors. https://dart-review.googlesource.com/c/sdk/+/262241 is an attempt at this which is blocked because it makes the type of manyUint8List
s polymorphic. This will be less of an issue once the implementation type is monomorphic, although some compiler work will still be required to optimize writes.When?
New factory constructors and deprecation should happen as soon as possible since it gates the other work.
Who?
@rakudrama
The text was updated successfully, but these errors were encountered: