Reuse field tails/offsets in VTable instances.

So, that we don't have to allocate these lists for each instance.
This makes it cheaper to have serialize more objects.

In a synthetic performance test, where we turn IDL builders into
bytes 100 times, this improves time to bytes from 45 to 34 seconds.

R=paulberry@google.com

Change-Id: Ie8c9a8ed01b4c0d4abeba96ed5e99bd430f9309d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104600
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/summary/flat_buffers.dart b/pkg/analyzer/lib/src/summary/flat_buffers.dart
index da60445..96d0719 100644
--- a/pkg/analyzer/lib/src/summary/flat_buffers.dart
+++ b/pkg/analyzer/lib/src/summary/flat_buffers.dart
@@ -89,6 +89,16 @@
   final int initialSize;
 
   /**
+   * The list of field tails, reused by [_VTable] instances.
+   */
+  final Int32List _reusedFieldTails = Int32List(1024);
+
+  /**
+   * The list of field offsets, reused by [_VTable] instances.
+   */
+  final Int32List _reusedFieldOffsets = Int32List(1024);
+
+  /**
    * The list of existing VTable(s).
    */
   final List<_VTable> _vTables = <_VTable>[];
@@ -248,6 +258,7 @@
       }
       // Write a new VTable.
       if (vTableTail == null) {
+        _currentVTable.takeFieldOffsets();
         _prepare(2, _currentVTable.numOfUint16);
         vTableTail = _tail;
         _currentVTable.tail = vTableTail;
@@ -332,7 +343,7 @@
     if (_currentVTable != null) {
       throw new StateError('Inline tables are not supported.');
     }
-    _currentVTable = new _VTable();
+    _currentVTable = new _VTable(_reusedFieldTails, _reusedFieldOffsets);
     _currentTableEndTail = _tail;
   }
 
@@ -570,6 +581,19 @@
 }
 
 /**
+ * The reader of 64-bit floats.
+ */
+class Float64Reader extends Reader<double> {
+  const Float64Reader() : super();
+
+  @override
+  int get size => 8;
+
+  @override
+  double read(BufferContext bc, int offset) => bc._getFloat64(offset);
+}
+
+/**
  * The reader of signed 32-bit integers.
  */
 class Int32Reader extends Reader<int> {
@@ -768,19 +792,6 @@
 }
 
 /**
- * The reader of 64-bit floats.
- */
-class Float64Reader extends Reader<double> {
-  const Float64Reader() : super();
-
-  @override
-  int get size => 8;
-
-  @override
-  double read(BufferContext bc, int offset) => bc._getFloat64(offset);
-}
-
-/**
  * List of booleans backed by 8-bit unsigned integers.
  */
 class _FbBoolList with ListMixin<bool> implements List<bool> {
@@ -905,8 +916,19 @@
  * Class that describes the structure of a table.
  */
 class _VTable {
-  final List<int> fieldTails = <int>[];
-  List<int> fieldOffsets;
+  final Int32List _reusedFieldTails;
+  final Int32List _reusedFieldOffsets;
+
+  /**
+   * The number of fields in [_reusedFieldTails].
+   */
+  int _fieldCount = 0;
+
+  /**
+   * The private copy of [_reusedFieldOffsets], which is made only when we
+   * find that this table is unique.
+   */
+  Int32List _fieldOffsets;
 
   /**
    * The size of the table that uses this VTable.
@@ -919,13 +941,15 @@
    */
   int tail;
 
-  int get numOfUint16 => 1 + 1 + fieldTails.length;
+  _VTable(this._reusedFieldTails, this._reusedFieldOffsets);
+
+  int get numOfUint16 => 1 + 1 + _fieldCount;
 
   void addField(int field, int offset) {
-    while (fieldTails.length <= field) {
-      fieldTails.add(null);
+    while (_fieldCount <= field) {
+      _reusedFieldTails[_fieldCount++] = -1;
     }
-    fieldTails[field] = offset;
+    _reusedFieldTails[field] = offset;
   }
 
   /**
@@ -935,9 +959,9 @@
     assert(tail == null);
     assert(existing.tail != null);
     if (tableSize == existing.tableSize &&
-        fieldOffsets.length == existing.fieldOffsets.length) {
-      for (int i = 0; i < fieldOffsets.length; i++) {
-        if (fieldOffsets[i] != existing.fieldOffsets[i]) {
+        _fieldCount == existing._fieldCount) {
+      for (int i = 0; i < _fieldCount; i++) {
+        if (_reusedFieldOffsets[i] != existing._fieldOffsets[i]) {
           return false;
         }
       }
@@ -947,14 +971,12 @@
   }
 
   /**
-   * Fill the [fieldOffsets] field.
+   * Fill the [_reusedFieldOffsets] field.
    */
   void computeFieldOffsets(int tableTail) {
-    assert(fieldOffsets == null);
-    fieldOffsets = List<int>(fieldTails.length);
-    for (int i = 0; i < fieldTails.length; ++i) {
-      int fieldTail = fieldTails[i];
-      fieldOffsets[i] = fieldTail == null ? 0 : tableTail - fieldTail;
+    for (int i = 0; i < _fieldCount; ++i) {
+      int fieldTail = _reusedFieldTails[i];
+      _reusedFieldOffsets[i] = fieldTail == -1 ? 0 : tableTail - fieldTail;
     }
   }
 
@@ -970,9 +992,20 @@
     buf.setUint16(bufOffset, tableSize, Endian.little);
     bufOffset += 2;
     // Field offsets.
-    for (int fieldOffset in fieldOffsets) {
+    for (int fieldOffset in _fieldOffsets) {
       buf.setUint16(bufOffset, fieldOffset, Endian.little);
       bufOffset += 2;
     }
   }
+
+  /**
+   * Fill the [_fieldOffsets] field.
+   */
+  void takeFieldOffsets() {
+    assert(_fieldOffsets == null);
+    _fieldOffsets = Int32List(_fieldCount);
+    for (int i = 0; i < _fieldCount; ++i) {
+      _fieldOffsets[i] = _reusedFieldOffsets[i];
+    }
+  }
 }