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
This commit is contained in:
Alex Buynytskyy
2021-08-10 11:20:56 -07:00
committed by Jeff Sharkey
parent 8a3803d401
commit 2be5b4c118
3 changed files with 64 additions and 8 deletions

View File

@@ -64,9 +64,13 @@ public class FastDataPerfTest {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) { while (state.keepRunning()) {
os.reset(); os.reset();
final FastDataOutput out = new FastDataOutput(os, BUFFER_SIZE); final FastDataOutput out = FastDataOutput.obtain(os);
doWrite(out); try {
out.flush(); doWrite(out);
out.flush();
} finally {
out.release();
}
} }
} }

View File

@@ -124,7 +124,7 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
mOut = new FastDataOutput(os, BUFFER_SIZE); mOut = FastDataOutput.obtain(os);
mOut.write(PROTOCOL_MAGIC_VERSION_0); mOut.write(PROTOCOL_MAGIC_VERSION_0);
mTagCount = 0; mTagCount = 0;
@@ -138,7 +138,9 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
mOut.flush(); if (mOut != null) {
mOut.flush();
}
} }
@Override @Override
@@ -157,6 +159,9 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
public void endDocument() throws IOException { public void endDocument() throws IOException {
mOut.writeByte(END_DOCUMENT | TYPE_NULL); mOut.writeByte(END_DOCUMENT | TYPE_NULL);
flush(); flush();
mOut.release();
mOut = null;
} }
@Override @Override

View File

@@ -30,6 +30,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Optimized implementation of {@link DataOutput} which buffers data in memory * 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 { public class FastDataOutput implements DataOutput, Flushable, Closeable {
private static final int MAX_UNSIGNED_SHORT = 65_535; 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 VMRuntime mRuntime;
private final OutputStream mOut;
private final byte[] mBuffer; private final byte[] mBuffer;
private final long mBufferPtr; private final long mBufferPtr;
private final int mBufferCap; private final int mBufferCap;
private OutputStream mOut;
private int mBufferPos; private int mBufferPos;
/** /**
* Values that have been "interned" by {@link #writeInternedUTF(String)}. * 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) { public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
mRuntime = VMRuntime.getRuntime(); mRuntime = VMRuntime.getRuntime();
mOut = Objects.requireNonNull(out);
if (bufferSize < 8) { if (bufferSize < 8) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
@@ -65,6 +69,48 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable {
mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize); mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
mBufferPtr = mRuntime.addressOf(mBuffer); mBufferPtr = mRuntime.addressOf(mBuffer);
mBufferCap = mBuffer.length; 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 { private void drain() throws IOException {
@@ -83,6 +129,7 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
mOut.close(); mOut.close();
release();
} }
@Override @Override