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