// 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`, constructed via `List.filled(...)`. // // There are two axes of benchmark variation: // 1. The queue element type (`int`, `int?`, `List`, 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` 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`. // ignore_for_file: prefer_final_locals import 'dart:typed_data'; import 'package:benchmark_harness/benchmark_harness.dart'; abstract class Benchmark extends BenchmarkBase { final int _iterations; final Queue _queue = Queue(); 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 _items = Queue(); @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 { 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 { 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> { 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 { 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 { 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().foo, z: BigInt.parse('42'))) // Type depends on function type variable: ..add((1, Env().bar(), z: DateTime.now())); } } class Env { T foo(T x) => x; S Function(T) bar() { 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() ..add('a') ..add('b') ..addAll({'a', 'b'}) ..addAll(['a', 'b']) ..toString(); Queue() ..add('a') ..add(RegExp('b')) ..toString(); Queue()..add(Env().foo); Queue() ..addAll([1]) ..addAll([2.3]); } void main() { final benchmarks = [ 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 implements Iterable { /// Creates a queue. factory Queue() = ListQueue; /// 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 iterable); /// Removes all elements in the queue. The size of the queue becomes zero. void clear(); } /// List based [Queue]. final class ListQueue implements Queue { static const int _INITIAL_CAPACITY = 8; List _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.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. @override Queue cast() => throw UnimplementedError('Iterable.cast'); @override Iterator get iterator => _ListQueueIterator(this); @override void forEach(void Function(E element) f) { int modificationCount = _modificationCount; for (int i = _head; i != _tail; i = (i + 1) & (_table.length - 1)) { f(_table[i] as E); _checkModification(modificationCount); } } @override bool get isEmpty => _head == _tail; @override bool get isNotEmpty => !isEmpty; @override int get length => (_tail - _head) & (_table.length - 1); @override E get first { if (_head == _tail) throw IterableElementError.noElement(); return _table[_head] as E; } @override E get last { if (_head == _tail) throw IterableElementError.noElement(); return _table[(_tail - 1) & (_table.length - 1)] as E; } @override E get single { if (_head == _tail) throw IterableElementError.noElement(); if (length > 1) throw IterableElementError.tooMany(); return _table[_head] as E; } @override E elementAt(int index) { IndexError.check(index, length, indexable: this); return _table[(_head + index) & (_table.length - 1)] as E; } @override List toList({bool growable = true}) { int mask = _table.length - 1; int length = (_tail - _head) & mask; if (length == 0) return List.empty(growable: growable); var list = List.filled(length, first, growable: growable); for (int i = 0; i < length; i++) { list[i] = _table[(_head + i) & mask] as E; } return list; } // Collection interface. @override void add(E value) { _add(value); } @override void addAll(Iterable elements) { if (elements is List) { List 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); } } } @override 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; } @override void clear() { if (_head != _tail) { for (int i = _head; i != _tail; i = (i + 1) & (_table.length - 1)) { _table[i] = null; } _head = _tail = 0; _modificationCount++; } } @override String toString() => Iterable.iterableToFullString(this, '{', '}'); // Queue interface. @override void addLast(E value) { _add(value); } @override void addFirst(E value) { _head = (_head - 1) & (_table.length - 1); _table[_head] = value; if (_head == _tail) _grow(); _modificationCount++; } @override 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; } @override 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 newTable = List.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 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 newTable = List.filled(newCapacity, null); _tail = _writeToList(newTable); _table = newTable; _head = 0; } /// Unimplemented Iterable methods @override Never any(_) => throw UnimplementedError(); @override Never contains(_) => throw UnimplementedError(); @override Never every(_) => throw UnimplementedError(); @override Never expand(_) => throw UnimplementedError(); @override Never firstWhere(_, {E Function()? orElse}) => throw UnimplementedError(); @override Never fold(_, _) => throw UnimplementedError(); @override Never followedBy(_) => throw UnimplementedError(); @override Never join([String _ = '']) => throw UnimplementedError(); @override Never lastWhere(_, {E Function()? orElse}) => throw UnimplementedError(); @override Never map(_) => throw UnimplementedError(); @override Never reduce(_) => throw UnimplementedError(); @override Never singleWhere(_, {E Function()? orElse}) => throw UnimplementedError(); @override Never skip(_) => throw UnimplementedError(); @override Never skipWhile(_) => throw UnimplementedError(); @override Never take(_) => throw UnimplementedError(); @override Never takeWhile(_) => throw UnimplementedError(); @override Never toSet() => throw UnimplementedError(); @override Never where(_) => throw UnimplementedError(); @override Never whereType() => throw UnimplementedError(); } /// Iterator for a [ListQueue]. /// /// Considers any add or remove operation a concurrent modification. class _ListQueueIterator implements Iterator { final ListQueue _queue; final int _end; final int _modificationCount; int _position; E? _current; _ListQueueIterator(ListQueue queue) : _queue = queue, _end = queue._tail, _modificationCount = queue._modificationCount, _position = queue._head; @override E get current => _current as E; @override 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'); }