From 2be5b4c118a77cf15544ce9f8d0464f2d416125c Mon Sep 17 00:00:00 2001
From: Alex Buynytskyy <alexbuy@google.com>
Date: Tue, 10 Aug 2021 11:20:56 -0700
Subject: [PATCH] Cache least recently used output buffer.

- reduce memory allocations especially when Xml is serialized inside a
tight inner loop,
- performance slightly improved.

BEFORE:
100 packages:
[2/4] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.516s)
	timeWrite_Binary_mean: 674979
	threadAllocCount_mean: 59
	timeWrite_Binary_min: 671194
	timeWrite_Binary_median: 674798
	timeWrite_Binary_standardDeviation: 3658
	threadAllocSize_mean: 530176
10 packages:
[2/4] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.542s)
	timeWrite_Binary_mean: 71113
	threadAllocCount_mean: 56
	timeWrite_Binary_min: 70201
	timeWrite_Binary_median: 71237
	timeWrite_Binary_standardDeviation: 692
	threadAllocSize_mean: 59136
1 package:
[2/4] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.091s)
	timeWrite_Binary_mean: 15698
	threadAllocCount_mean: 56
	timeWrite_Binary_min: 15072
	timeWrite_Binary_median: 15677
	timeWrite_Binary_standardDeviation: 410
	threadAllocSize_mean: 6439

AFTER:
100 packages:
[1/1] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.221s)
	timeWrite_Binary_mean: 605862
	threadAllocCount_mean: 53
	timeWrite_Binary_min: 602353
	timeWrite_Binary_median: 604424
	timeWrite_Binary_standardDeviation: 4972
	threadAllocSize_mean: 492748
10 packages:
[1/1] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.575s)
	timeWrite_Binary_mean: 58710
	threadAllocCount_mean: 50
	timeWrite_Binary_min: 57551
	timeWrite_Binary_median: 57798
	timeWrite_Binary_standardDeviation: 2129
	threadAllocSize_mean: 21696
1 package:
[1/1] android.util.XmlPerfTest#timeWrite_Binary: PASSED (10.151s)
	timeWrite_Binary_mean: 11576
	threadAllocCount_mean: 50
	timeWrite_Binary_min: 11315
	timeWrite_Binary_median: 11381
	timeWrite_Binary_standardDeviation: 431
	threadAllocSize_mean: 4336

Test: atest FastDataPerfTest XmlPerfTest
Bug: 195994150
Fixes: 195994150

Merged-In: Ieedd27a676d718967c2fac30bc48a68ae636a180
Change-Id: Ieedd27a676d718967c2fac30bc48a68ae636a180
---
 .../internal/util/FastDataPerfTest.java       | 10 ++--
 .../internal/util/BinaryXmlSerializer.java    |  9 +++-
 .../android/internal/util/FastDataOutput.java | 53 +++++++++++++++++--
 3 files changed, 64 insertions(+), 8 deletions(-)

diff --git a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
index 2700fff4cba1..e3691a783bd6 100644
--- a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
+++ b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
@@ -64,9 +64,13 @@ public class FastDataPerfTest {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             os.reset();
-            final FastDataOutput out = new FastDataOutput(os, BUFFER_SIZE);
-            doWrite(out);
-            out.flush();
+            final FastDataOutput out = FastDataOutput.obtain(os);
+            try {
+                doWrite(out);
+                out.flush();
+            } finally {
+                out.release();
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java
index 9df4bdb157c8..f0ca1edb0b90 100644
--- a/core/java/com/android/internal/util/BinaryXmlSerializer.java
+++ b/core/java/com/android/internal/util/BinaryXmlSerializer.java
@@ -124,7 +124,7 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
             throw new UnsupportedOperationException();
         }
 
-        mOut = new FastDataOutput(os, BUFFER_SIZE);
+        mOut = FastDataOutput.obtain(os);
         mOut.write(PROTOCOL_MAGIC_VERSION_0);
 
         mTagCount = 0;
@@ -138,7 +138,9 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
 
     @Override
     public void flush() throws IOException {
-        mOut.flush();
+        if (mOut != null) {
+            mOut.flush();
+        }
     }
 
     @Override
@@ -157,6 +159,9 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
     public void endDocument() throws IOException {
         mOut.writeByte(END_DOCUMENT | TYPE_NULL);
         flush();
+
+        mOut.release();
+        mOut = null;
     }
 
     @Override
diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java
index cf5b2966d889..bc8496b3bdd3 100644
--- a/core/java/com/android/internal/util/FastDataOutput.java
+++ b/core/java/com/android/internal/util/FastDataOutput.java
@@ -30,6 +30,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Optimized implementation of {@link DataOutput} which buffers data in memory
@@ -41,23 +42,26 @@ import java.util.Objects;
 public class FastDataOutput implements DataOutput, Flushable, Closeable {
     private static final int MAX_UNSIGNED_SHORT = 65_535;
 
+    private static final int BUFFER_SIZE = 32_768;
+
+    private static AtomicReference<FastDataOutput> sOutCache = new AtomicReference<>();
+
     private final VMRuntime mRuntime;
-    private final OutputStream mOut;
 
     private final byte[] mBuffer;
     private final long mBufferPtr;
     private final int mBufferCap;
 
+    private OutputStream mOut;
     private int mBufferPos;
 
     /**
      * Values that have been "interned" by {@link #writeInternedUTF(String)}.
      */
-    private HashMap<String, Short> mStringRefs = new HashMap<>();
+    private final HashMap<String, Short> mStringRefs = new HashMap<>();
 
     public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
         mRuntime = VMRuntime.getRuntime();
-        mOut = Objects.requireNonNull(out);
         if (bufferSize < 8) {
             throw new IllegalArgumentException();
         }
@@ -65,6 +69,48 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable {
         mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
         mBufferPtr = mRuntime.addressOf(mBuffer);
         mBufferCap = mBuffer.length;
+
+        setOutput(out);
+    }
+
+    /**
+     * Create a new FastDataOutput object or retrieve one from cache.
+     */
+    public static FastDataOutput obtain(@NonNull OutputStream out) {
+        FastDataOutput instance = sOutCache.getAndSet(null);
+        if (instance != null) {
+            instance.setOutput(out);
+            return instance;
+        }
+        return new FastDataOutput(out, BUFFER_SIZE);
+    }
+
+    /**
+     * Put a FastDataOutput object back into the cache.
+     * You must not touch the object after this call.
+     */
+    public void release() {
+        if (mBufferPos > 0) {
+            throw new IllegalStateException("Lingering data, call flush() before releasing.");
+        }
+
+        mOut = null;
+        mBufferPos = 0;
+        mStringRefs.clear();
+
+        if (mBufferCap == BUFFER_SIZE) {
+            // Try to return to the cache.
+            sOutCache.compareAndSet(null, this);
+        }
+    }
+
+    /**
+     * Re-initializes the object for the new output.
+     */
+    private void setOutput(@NonNull OutputStream out) {
+        mOut = Objects.requireNonNull(out);
+        mBufferPos = 0;
+        mStringRefs.clear();
     }
 
     private void drain() throws IOException {
@@ -83,6 +129,7 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable {
     @Override
     public void close() throws IOException {
         mOut.close();
+        release();
     }
 
     @Override
-- 
GitLab