[benchmarks] Queue add/remove benchmark
Change-Id: I98ba55744c86ed3e39d0087f4fcb83e4a2a63f68 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408240 Commit-Queue: Stephen Adams <sra@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
b732a62fd2
commit
4a62cface0
@@ -0,0 +1,662 @@
|
||||
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Invariance optimizations benchmark based on adding and removing from a copy
|
||||
// of the default implementation of Queue.
|
||||
//
|
||||
// This benchmark incurs type checks due to parametric subtype covariance. Many
|
||||
// of these type checks can be optimized away by invariance analysis to discover
|
||||
// which types are invariant.
|
||||
//
|
||||
// Some compilers already optimize '`this`-calls`, where the receiver is
|
||||
// `this`. `this`-calls are a trivial kind of invariance since the caller and
|
||||
// callee share the same receiver, so share the same class type variables. An
|
||||
// example of this is when `ListQueue.add` calls `ListQueue._add`. There is no
|
||||
// need to check `value as E` again.
|
||||
//
|
||||
// One example that is not well optimized at the time of writing is when type
|
||||
// variables of related collections are invariant. The Queue implementation
|
||||
// uses, as a backing store, a fixed length list that has a type invariant with
|
||||
// respect to the `Queue<E>`, constructed via `List<E?>.filled(...)`.
|
||||
//
|
||||
// There are two axes of benchmark variation:
|
||||
// 1. The queue element type (`int`, `int?`, `List<int>`, records)
|
||||
// 2. Adding elements via `Queue.add` and `Queue.addAll`.
|
||||
//
|
||||
// The first axis exercises a variety of covariance checks. More complex checks
|
||||
// tend to be slower, magnifying the benefit of invariance analysis.
|
||||
//
|
||||
// The second axis, using `add` and `addAll`, adds more opportunities for
|
||||
// invariances to be detected (`_queue` and `_items` are separate collections
|
||||
// that have the same value of the type variable).
|
||||
//
|
||||
// All benchmarks use the workload defined in `Benchmark<T>` so that the
|
||||
// compilers don't achieve optimizations by flowing constant types. We avoid
|
||||
// mixins because some implementations expand mixins, which could result in the
|
||||
// expanded code having non-parametric types, e.g. `Queue<int>`.
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
||||
|
||||
abstract class Benchmark<T> extends BenchmarkBase {
|
||||
final int _iterations;
|
||||
final Queue<T> _queue = Queue<T>();
|
||||
|
||||
final bool _runAddAll;
|
||||
|
||||
Benchmark(String name, String type, this._iterations)
|
||||
: _runAddAll = name.startsWith('addAll'),
|
||||
super('InvarianceQueue.$name.$type');
|
||||
|
||||
// For the add/remove benchmarks, values are copied through this field. `T` is
|
||||
// invariant with respect to the Queue element type.
|
||||
T? _item;
|
||||
|
||||
// For the addAll/remove benchmarks, values are copied through this second
|
||||
// Queue which contains one element at the time of copy.
|
||||
final Queue<T> _items = Queue<T>();
|
||||
|
||||
@override
|
||||
void run() {
|
||||
if (_runAddAll) {
|
||||
runAddAllRemove();
|
||||
} else {
|
||||
runAddRemove();
|
||||
}
|
||||
}
|
||||
|
||||
void check() {
|
||||
if (_runAddAll) {
|
||||
checkAddAllRemove();
|
||||
} else {
|
||||
checkAddRemove();
|
||||
}
|
||||
}
|
||||
|
||||
void runAddRemove() {
|
||||
for (int i = 0; i < _iterations; i++) {
|
||||
_item = _queue.removeFirst();
|
||||
_queue.add(_item as T);
|
||||
}
|
||||
}
|
||||
|
||||
void checkAddRemove() {
|
||||
if (_item == _queue.first) throw StateError('bad');
|
||||
}
|
||||
|
||||
void runAddAllRemove() {
|
||||
for (int i = 0; i < _iterations; i++) {
|
||||
_items
|
||||
..clear()
|
||||
..add(_queue.removeFirst());
|
||||
_queue.addAll(_items);
|
||||
}
|
||||
}
|
||||
|
||||
void checkAddAllRemove() {
|
||||
if (_items.single == _queue.first) throw StateError('bad');
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueueInt extends Benchmark<int> {
|
||||
QueueInt(String name, int count) : super(name, 'int', count);
|
||||
@override
|
||||
void setup() {
|
||||
_queue
|
||||
..clear()
|
||||
..add(1)
|
||||
..add(2)
|
||||
..add(3)
|
||||
..add(4);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueueIntQ extends Benchmark<int?> {
|
||||
QueueIntQ(String name, int count) : super(name, 'intQ', count);
|
||||
@override
|
||||
void setup() {
|
||||
_queue
|
||||
..clear()
|
||||
..add(1)
|
||||
..add(2)
|
||||
..add(3)
|
||||
..add(null);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueueListInt extends Benchmark<List<int>> {
|
||||
QueueListInt(String name, int count) : super(name, 'List.int', count);
|
||||
@override
|
||||
void setup() {
|
||||
_queue
|
||||
..clear()
|
||||
..add('1'.codeUnits)
|
||||
..add([2])
|
||||
..add(Uint8List(3))
|
||||
..add(Int16List(4));
|
||||
}
|
||||
}
|
||||
|
||||
typedef SimpleRecord = (int, int);
|
||||
typedef ComplexRecord = (num, num Function(num), {Comparable z});
|
||||
|
||||
abstract class QueueSimpleRecord extends Benchmark<SimpleRecord> {
|
||||
QueueSimpleRecord(String name, int count)
|
||||
: super(name, 'simple-record', count);
|
||||
@override
|
||||
void setup() {
|
||||
_queue
|
||||
..clear()
|
||||
..add((0, 1))
|
||||
..add((1, 2))
|
||||
..add((2, 3))
|
||||
..add((3, 0));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueueComplexRecord extends Benchmark<ComplexRecord> {
|
||||
QueueComplexRecord(String name, int count)
|
||||
: super(name, 'complex-record', count);
|
||||
@override
|
||||
void setup() {
|
||||
_queue
|
||||
..clear()
|
||||
..add((1.2, 1.2.compareTo, z: 'x'))
|
||||
..add((0, (x) => x + 1, z: 123))
|
||||
// Type depends on class type variable:
|
||||
..add((1, Env<num>().foo, z: BigInt.parse('42')))
|
||||
// Type depends on function type variable:
|
||||
..add((1, Env<num>().bar<int>(), z: DateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
class Env<T> {
|
||||
T foo(T x) => x;
|
||||
|
||||
S Function(T) bar<S extends T>() {
|
||||
S localFunction(T x) {
|
||||
if (x is S) return x;
|
||||
throw 'bad';
|
||||
}
|
||||
|
||||
return localFunction;
|
||||
}
|
||||
}
|
||||
|
||||
class QueueAddRemoveInt extends QueueInt {
|
||||
QueueAddRemoveInt(int count) : super('add-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddRemoveIntQ extends QueueIntQ {
|
||||
QueueAddRemoveIntQ(int count) : super('add-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddRemoveListInt extends QueueListInt {
|
||||
QueueAddRemoveListInt(int count) : super('add-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddRemoveSimpleRecord extends QueueSimpleRecord {
|
||||
QueueAddRemoveSimpleRecord(int count) : super('add-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddRemoveComplexRecord extends QueueComplexRecord {
|
||||
QueueAddRemoveComplexRecord(int count) : super('add-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddAllRemoveInt extends QueueInt {
|
||||
QueueAddAllRemoveInt(int count) : super('addAll-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddAllRemoveIntQ extends QueueIntQ {
|
||||
QueueAddAllRemoveIntQ(int count) : super('addAll-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddAllRemoveListInt extends QueueListInt {
|
||||
QueueAddAllRemoveListInt(int count) : super('addAll-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddAllRemoveSimpleRecord extends QueueSimpleRecord {
|
||||
QueueAddAllRemoveSimpleRecord(int count) : super('addAll-remove', count);
|
||||
}
|
||||
|
||||
class QueueAddAllRemoveComplexRecord extends QueueComplexRecord {
|
||||
QueueAddAllRemoveComplexRecord(int count) : super('addAll-remove', count);
|
||||
}
|
||||
|
||||
void pollute() {
|
||||
// Pollute run size.
|
||||
QueueAddRemoveInt(0)
|
||||
..setup()
|
||||
..run();
|
||||
QueueAddRemoveIntQ(1)
|
||||
..setup()
|
||||
..run();
|
||||
QueueAddRemoveListInt(2)
|
||||
..setup()
|
||||
..run();
|
||||
// Further pollute type.
|
||||
Queue<String>()
|
||||
..add('a')
|
||||
..add('b')
|
||||
..addAll({'a', 'b'})
|
||||
..addAll(['a', 'b'])
|
||||
..toString();
|
||||
Queue<Pattern>()
|
||||
..add('a')
|
||||
..add(RegExp('b'))
|
||||
..toString();
|
||||
Queue<String Function(String)>()..add(Env<String>().foo);
|
||||
Queue<num>()
|
||||
..addAll(<int>[1])
|
||||
..addAll(<double>[2.3]);
|
||||
}
|
||||
|
||||
void main() {
|
||||
final benchmarks = <Benchmark>[
|
||||
QueueAddRemoveInt(1000),
|
||||
QueueAddRemoveIntQ(1000),
|
||||
QueueAddRemoveListInt(1000),
|
||||
QueueAddRemoveSimpleRecord(1000),
|
||||
QueueAddRemoveComplexRecord(1000),
|
||||
QueueAddAllRemoveInt(1000),
|
||||
QueueAddAllRemoveIntQ(1000),
|
||||
QueueAddAllRemoveListInt(1000),
|
||||
QueueAddAllRemoveSimpleRecord(1000),
|
||||
QueueAddAllRemoveComplexRecord(1000),
|
||||
];
|
||||
|
||||
// Warmup all benchmarks to ensure JIT compilers see full polymorphism.
|
||||
for (final benchmark in benchmarks) {
|
||||
pollute();
|
||||
benchmark.setup();
|
||||
}
|
||||
|
||||
for (final benchmark in benchmarks) {
|
||||
pollute();
|
||||
benchmark.warmup();
|
||||
}
|
||||
|
||||
for (final benchmark in benchmarks) {
|
||||
// `report` calls `setup`, but `setup` is idempotent.
|
||||
benchmark.report();
|
||||
benchmark.check();
|
||||
}
|
||||
}
|
||||
|
||||
/// What follows is a copy of the SDK Queue implementation, cut down to remove
|
||||
/// methods that are unused in this benchmark.
|
||||
|
||||
/// A [Queue] is a collection that can be manipulated at both ends.
|
||||
abstract interface class Queue<E> implements Iterable<E> {
|
||||
/// Creates a queue.
|
||||
factory Queue() = ListQueue<E>;
|
||||
|
||||
/// Removes and returns the first element of this queue.
|
||||
E removeFirst();
|
||||
|
||||
/// Removes and returns the last element of the queue.
|
||||
E removeLast();
|
||||
|
||||
/// Adds [value] at the beginning of the queue.
|
||||
void addFirst(E value);
|
||||
|
||||
/// Adds [value] at the end of the queue.
|
||||
void addLast(E value);
|
||||
|
||||
/// Adds [value] at the end of the queue.
|
||||
void add(E value);
|
||||
|
||||
/// Removes a single instance of [value] from the queue.
|
||||
///
|
||||
/// Returns `true` if a value was removed, or `false` if the queue
|
||||
/// contained no element equal to [value].
|
||||
bool remove(Object? value);
|
||||
|
||||
/// Adds all elements of [iterable] at the end of the queue. The
|
||||
/// length of the queue is extended by the length of [iterable].
|
||||
void addAll(Iterable<E> iterable);
|
||||
|
||||
/// Removes all elements in the queue. The size of the queue becomes zero.
|
||||
void clear();
|
||||
}
|
||||
|
||||
/// List based [Queue].
|
||||
final class ListQueue<E> implements Queue<E> {
|
||||
static const int _INITIAL_CAPACITY = 8;
|
||||
List<E?> _table;
|
||||
int _head;
|
||||
int _tail;
|
||||
int _modificationCount = 0;
|
||||
|
||||
/// Create an empty queue.
|
||||
///
|
||||
/// If [initialCapacity] is given, prepare the queue for at least that many
|
||||
/// elements.
|
||||
ListQueue([int? initialCapacity])
|
||||
: _head = 0,
|
||||
_tail = 0,
|
||||
_table = List<E?>.filled(_calculateCapacity(initialCapacity), null);
|
||||
|
||||
static int _calculateCapacity(int? initialCapacity) {
|
||||
if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
|
||||
return _INITIAL_CAPACITY;
|
||||
} else if (!_isPowerOf2(initialCapacity)) {
|
||||
return _nextPowerOf2(initialCapacity);
|
||||
}
|
||||
assert(_isPowerOf2(initialCapacity));
|
||||
return initialCapacity;
|
||||
}
|
||||
|
||||
// Iterable interface.
|
||||
|
||||
Queue<R> cast<R>() => throw UnimplementedError('Iteratble.cast');
|
||||
|
||||
Iterator<E> get iterator => _ListQueueIterator<E>(this);
|
||||
|
||||
void forEach(void f(E element)) {
|
||||
int modificationCount = _modificationCount;
|
||||
for (int i = _head; i != _tail; i = (i + 1) & (_table.length - 1)) {
|
||||
f(_table[i] as E);
|
||||
_checkModification(modificationCount);
|
||||
}
|
||||
}
|
||||
|
||||
bool get isEmpty => _head == _tail;
|
||||
|
||||
bool get isNotEmpty => !isEmpty;
|
||||
|
||||
int get length => (_tail - _head) & (_table.length - 1);
|
||||
|
||||
E get first {
|
||||
if (_head == _tail) throw IterableElementError.noElement();
|
||||
return _table[_head] as E;
|
||||
}
|
||||
|
||||
E get last {
|
||||
if (_head == _tail) throw IterableElementError.noElement();
|
||||
return _table[(_tail - 1) & (_table.length - 1)] as E;
|
||||
}
|
||||
|
||||
E get single {
|
||||
if (_head == _tail) throw IterableElementError.noElement();
|
||||
if (length > 1) throw IterableElementError.tooMany();
|
||||
return _table[_head] as E;
|
||||
}
|
||||
|
||||
E elementAt(int index) {
|
||||
IndexError.check(index, length, indexable: this);
|
||||
return _table[(_head + index) & (_table.length - 1)] as E;
|
||||
}
|
||||
|
||||
List<E> toList({bool growable = true}) {
|
||||
int mask = _table.length - 1;
|
||||
int length = (_tail - _head) & mask;
|
||||
if (length == 0) return List<E>.empty(growable: growable);
|
||||
|
||||
var list = List<E>.filled(length, first, growable: growable);
|
||||
for (int i = 0; i < length; i++) {
|
||||
list[i] = _table[(_head + i) & mask] as E;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// Collection interface.
|
||||
|
||||
void add(E value) {
|
||||
_add(value);
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> elements) {
|
||||
if (elements is List<E>) {
|
||||
List<E> list = elements;
|
||||
int addCount = list.length;
|
||||
int length = this.length;
|
||||
if (length + addCount >= _table.length) {
|
||||
_preGrow(length + addCount);
|
||||
// After preGrow, all elements are at the start of the list.
|
||||
_table.setRange(length, length + addCount, list, 0);
|
||||
_tail += addCount;
|
||||
} else {
|
||||
// Adding addCount elements won't reach _head.
|
||||
int endSpace = _table.length - _tail;
|
||||
if (addCount < endSpace) {
|
||||
_table.setRange(_tail, _tail + addCount, list, 0);
|
||||
_tail += addCount;
|
||||
} else {
|
||||
int preSpace = addCount - endSpace;
|
||||
_table.setRange(_tail, _tail + endSpace, list, 0);
|
||||
_table.setRange(0, preSpace, list, endSpace);
|
||||
_tail = preSpace;
|
||||
}
|
||||
}
|
||||
_modificationCount++;
|
||||
} else {
|
||||
for (E element in elements) _add(element);
|
||||
}
|
||||
}
|
||||
|
||||
bool remove(Object? value) {
|
||||
for (int i = _head; i != _tail; i = (i + 1) & (_table.length - 1)) {
|
||||
E? element = _table[i];
|
||||
if (element == value) {
|
||||
_remove(i);
|
||||
_modificationCount++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (_head != _tail) {
|
||||
for (int i = _head; i != _tail; i = (i + 1) & (_table.length - 1)) {
|
||||
_table[i] = null;
|
||||
}
|
||||
_head = _tail = 0;
|
||||
_modificationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => Iterable.iterableToFullString(this, "{", "}");
|
||||
|
||||
// Queue interface.
|
||||
|
||||
void addLast(E value) {
|
||||
_add(value);
|
||||
}
|
||||
|
||||
void addFirst(E value) {
|
||||
_head = (_head - 1) & (_table.length - 1);
|
||||
_table[_head] = value;
|
||||
if (_head == _tail) _grow();
|
||||
_modificationCount++;
|
||||
}
|
||||
|
||||
E removeFirst() {
|
||||
if (_head == _tail) throw IterableElementError.noElement();
|
||||
_modificationCount++;
|
||||
E result = _table[_head] as E;
|
||||
_table[_head] = null;
|
||||
_head = (_head + 1) & (_table.length - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
E removeLast() {
|
||||
if (_head == _tail) throw IterableElementError.noElement();
|
||||
_modificationCount++;
|
||||
_tail = (_tail - 1) & (_table.length - 1);
|
||||
E result = _table[_tail] as E;
|
||||
_table[_tail] = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Internal helper functions.
|
||||
|
||||
/// Whether [number] is a power of two.
|
||||
///
|
||||
/// Only works for positive numbers.
|
||||
static bool _isPowerOf2(int number) => (number & (number - 1)) == 0;
|
||||
|
||||
/// Rounds [number] up to the nearest power of 2.
|
||||
///
|
||||
/// If [number] is a power of 2 already, it is returned.
|
||||
///
|
||||
/// Only works for positive numbers.
|
||||
static int _nextPowerOf2(int number) {
|
||||
assert(number > 0);
|
||||
number = (number << 1) - 1;
|
||||
for (;;) {
|
||||
int nextNumber = number & (number - 1);
|
||||
if (nextNumber == 0) return number;
|
||||
number = nextNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the queue has been modified during iteration.
|
||||
void _checkModification(int expectedModificationCount) {
|
||||
if (expectedModificationCount != _modificationCount) {
|
||||
throw ConcurrentModificationError(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds element at end of queue. Used by both [add] and [addAll].
|
||||
void _add(E element) {
|
||||
_table[_tail] = element;
|
||||
_tail = (_tail + 1) & (_table.length - 1);
|
||||
if (_head == _tail) _grow();
|
||||
_modificationCount++;
|
||||
}
|
||||
|
||||
/// Removes the element at [offset] into [_table].
|
||||
///
|
||||
/// Removal is performed by linearly moving elements either before or after
|
||||
/// [offset] by one position.
|
||||
///
|
||||
/// Returns the new offset of the following element. This may be the same
|
||||
/// offset or the following offset depending on how elements are moved
|
||||
/// to fill the hole.
|
||||
int _remove(int offset) {
|
||||
int mask = _table.length - 1;
|
||||
int startDistance = (offset - _head) & mask;
|
||||
int endDistance = (_tail - offset) & mask;
|
||||
if (startDistance < endDistance) {
|
||||
// Closest to start.
|
||||
int i = offset;
|
||||
while (i != _head) {
|
||||
int prevOffset = (i - 1) & mask;
|
||||
_table[i] = _table[prevOffset];
|
||||
i = prevOffset;
|
||||
}
|
||||
_table[_head] = null;
|
||||
_head = (_head + 1) & mask;
|
||||
return (offset + 1) & mask;
|
||||
} else {
|
||||
_tail = (_tail - 1) & mask;
|
||||
int i = offset;
|
||||
while (i != _tail) {
|
||||
int nextOffset = (i + 1) & mask;
|
||||
_table[i] = _table[nextOffset];
|
||||
i = nextOffset;
|
||||
}
|
||||
_table[_tail] = null;
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// Grow the table when full.
|
||||
void _grow() {
|
||||
List<E?> newTable = List<E?>.filled(_table.length * 2, null);
|
||||
int split = _table.length - _head;
|
||||
newTable.setRange(0, split, _table, _head);
|
||||
newTable.setRange(split, split + _head, _table, 0);
|
||||
_head = 0;
|
||||
_tail = _table.length;
|
||||
_table = newTable;
|
||||
}
|
||||
|
||||
int _writeToList(List<E?> target) {
|
||||
assert(target.length >= length);
|
||||
if (_head <= _tail) {
|
||||
int length = _tail - _head;
|
||||
target.setRange(0, length, _table, _head);
|
||||
return length;
|
||||
} else {
|
||||
int firstPartSize = _table.length - _head;
|
||||
target.setRange(0, firstPartSize, _table, _head);
|
||||
target.setRange(firstPartSize, firstPartSize + _tail, _table, 0);
|
||||
return _tail + firstPartSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// Grows the table even if it is not full.
|
||||
void _preGrow(int newElementCount) {
|
||||
assert(newElementCount >= length);
|
||||
|
||||
// Add some extra room to ensure that there's room for more elements after
|
||||
// expansion.
|
||||
newElementCount += newElementCount >> 1;
|
||||
int newCapacity = _nextPowerOf2(newElementCount);
|
||||
List<E?> newTable = List<E?>.filled(newCapacity, null);
|
||||
_tail = _writeToList(newTable);
|
||||
_table = newTable;
|
||||
_head = 0;
|
||||
}
|
||||
|
||||
/// Unimplemented Iterable methods
|
||||
|
||||
Never any(_) => throw UnimplementedError();
|
||||
Never contains(_) => throw UnimplementedError();
|
||||
Never every(_) => throw UnimplementedError();
|
||||
Never expand<T>(_) => throw UnimplementedError();
|
||||
Never firstWhere(_, {E orElse()?}) => throw UnimplementedError();
|
||||
Never fold<E>(_, _) => throw UnimplementedError();
|
||||
Never followedBy(_) => throw UnimplementedError();
|
||||
Never join([String _ = '']) => throw UnimplementedError();
|
||||
Never lastWhere(_, {E orElse()?}) => throw UnimplementedError();
|
||||
Never map<T>(_) => throw UnimplementedError();
|
||||
Never reduce(_) => throw UnimplementedError();
|
||||
Never singleWhere(_, {E orElse()?}) => throw UnimplementedError();
|
||||
Never skip(_) => throw UnimplementedError();
|
||||
Never skipWhile(_) => throw UnimplementedError();
|
||||
Never take(_) => throw UnimplementedError();
|
||||
Never takeWhile(_) => throw UnimplementedError();
|
||||
Never toSet() => throw UnimplementedError();
|
||||
Never where(_) => throw UnimplementedError();
|
||||
Never whereType<T>() => throw UnimplementedError();
|
||||
}
|
||||
|
||||
/// Iterator for a [ListQueue].
|
||||
///
|
||||
/// Considers any add or remove operation a concurrent modification.
|
||||
class _ListQueueIterator<E> implements Iterator<E> {
|
||||
final ListQueue<E> _queue;
|
||||
final int _end;
|
||||
final int _modificationCount;
|
||||
int _position;
|
||||
E? _current;
|
||||
|
||||
_ListQueueIterator(ListQueue<E> queue)
|
||||
: _queue = queue,
|
||||
_end = queue._tail,
|
||||
_modificationCount = queue._modificationCount,
|
||||
_position = queue._head;
|
||||
|
||||
E get current => _current as E;
|
||||
|
||||
bool moveNext() {
|
||||
_queue._checkModification(_modificationCount);
|
||||
if (_position == _end) {
|
||||
_current = null;
|
||||
return false;
|
||||
}
|
||||
_current = _queue._table[_position];
|
||||
_position = (_position + 1) & (_queue._table.length - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class IterableElementError {
|
||||
static StateError noElement() => StateError("No element");
|
||||
static StateError tooMany() => StateError("Too many elements");
|
||||
}
|
||||
Reference in New Issue
Block a user