Add Typeface memory perf test.

This test measures:
- SharedMemory (mmap) byte size of serialized font map.
- Native allocation (malloc) byte size of deserialized font map.

Typeface is updated to avoid OOM during test.

Results on oriole-userdebug:

[1/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap: PASSED (29.222s)
	testSerializeFontMap_stddev (ns): 74859
	testSerializeFontMap_mean (ns): 1560404
	testSerializeFontMap_median (ns): 1549601
	testSerializeFontMap_percentile90 (ns): 1602499
	testSerializeFontMap_percentile95 (ns): 1624715
[2/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap_memory: PASSED (13.376s)
	testSerializeFontMap_memory_stddev (ns): 0
	testSerializeFontMap_memory_mean (ns): 931071
	testSerializeFontMap_memory_median (ns): 931071
	testSerializeFontMap_memory_percentile90 (ns): 931071
	testSerializeFontMap_memory_percentile95 (ns): 931071
[3/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap: PASSED (32.451s)
	testDeserializeFontMap_stddev (ns): 60524
	testDeserializeFontMap_mean (ns): 232285
	testDeserializeFontMap_median (ns): 228149
	testDeserializeFontMap_percentile90 (ns): 272094
	testDeserializeFontMap_percentile95 (ns): 291708
[4/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap_memory: PASSED (29.089s)
	testDeserializeFontMap_memory_stddev (ns): 134
	testDeserializeFontMap_memory_mean (ns): 170209
	testDeserializeFontMap_memory_median (ns): 170208
	testDeserializeFontMap_memory_percentile90 (ns): 170208
	testDeserializeFontMap_memory_percentile95 (ns): 170208
[5/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSetSystemFontMap: PASSED (28.687s)
	testSetSystemFontMap_stddev (ns): 2345934
	testSetSystemFontMap_mean (ns): 649662
	testSetSystemFontMap_median (ns): 396260
	testSetSystemFontMap_percentile90 (ns): 436483
	testSetSystemFontMap_percentile95 (ns): 457479

Bug: 174672300
Fix: 238679890
Test: atest CorePerfTests:android.graphics.perftests.TypefaceSerializationPerfTest
Change-Id: Idb0c2a040d3ca488134f2b91a5bcbbe33a7a59c4
This commit is contained in:
Kohsuke Yatoh
2022-07-15 06:03:07 +00:00
parent f99e2e7eb6
commit 9feb92f945
3 changed files with 104 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.perftests.core">
<permission android:name="com.android.perftests.core.TestPermission" />
@@ -33,6 +34,17 @@
android:exported="false"
android:process=":some_provider" />
<!-- We remove EmojiCompat initializer here because it may crash the test process
if the initializer runs while TypefaceSerializationPerfTest is running. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
tools:node="remove" />
</provider>
<service
android:name="android.view.autofill.MyAutofillService"
android:label="PERF AutofillService"

View File

@@ -17,10 +17,13 @@
package android.graphics.perftests;
import android.graphics.Typeface;
import android.os.Debug;
import android.os.SharedMemory;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.PerfManualStatusReporter;
import android.util.ArrayMap;
import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,16 +40,33 @@ import java.util.Map;
@RunWith(AndroidJUnit4.class)
public class TypefaceSerializationPerfTest {
private static final String TAG = "TypefaceSerializationPerfTest";
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
@Test
public void testSerializeFontMap() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
long elapsedTime = 0;
while (state.keepRunning(elapsedTime)) {
long startTime = System.nanoTime();
Typeface.serializeFontMap(systemFontMap);
elapsedTime = System.nanoTime() - startTime;
}
}
@Test
public void testSerializeFontMap_memory() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
SharedMemory memory = Typeface.serializeFontMap(systemFontMap);
ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
while (state.keepRunning(memory.getSize())) {
// Rate-limiting
SystemClock.sleep(100);
}
}
@@ -54,22 +74,54 @@ public class TypefaceSerializationPerfTest {
public void testDeserializeFontMap() throws Exception {
SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
ArrayMap<String, Typeface> out = new ArrayMap<>();
while (state.keepRunning()) {
long elapsedTime = 0;
while (state.keepRunning(elapsedTime)) {
long startTime = System.nanoTime();
buffer.position(0);
Typeface.deserializeFontMap(buffer, out);
elapsedTime = System.nanoTime() - startTime;
}
}
@Test
public void testDeserializeFontMap_memory() throws Exception {
SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
ArrayMap<String, Typeface> out = new ArrayMap<>();
// Diff of native heap allocation size (in bytes) before and after deserializeFontMap.
// Note: we don't measure memory usage of setSystemFontMap because setSystemFontMap sets
// some global variables, and it's hard to clear them.
long heapDiff = 0;
// Sometimes heapDiff may become negative due to GC.
// Use 0 in that case to avoid crashing in keepRunning.
while (state.keepRunning(Math.max(0, heapDiff))) {
buffer.position(0);
long baselineSize = Debug.getNativeHeapAllocatedSize();
Typeface.deserializeFontMap(buffer, out);
long currentSize = Debug.getNativeHeapAllocatedSize();
heapDiff = currentSize - baselineSize;
Log.i(TAG, String.format("native heap alloc: current = %d, baseline = %d, diff = %d",
currentSize, baselineSize, heapDiff));
// Release native objects here to minimize the impact of GC.
for (Typeface typeface : out.values()) {
typeface.releaseNativeObjectForTest();
}
out.clear();
}
}
@Test
public void testSetSystemFontMap() throws Exception {
SharedMemory memory = null;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
long elapsedTime = 0;
while (state.keepRunning(elapsedTime)) {
// Explicitly destroy lazy-loaded typefaces, so that we don't hit the mmap limit
// (max_map_count).
Typeface.destroySystemFontMap();
@@ -78,8 +130,9 @@ public class TypefaceSerializationPerfTest {
memory.close();
}
memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
state.resumeTiming();
long startTime = System.nanoTime();
Typeface.setSystemFontMap(memory);
elapsedTime = System.nanoTime() - startTime;
}
}
}

View File

@@ -1176,6 +1176,17 @@ public class Typeface {
mWeight = nativeGetWeight(ni);
}
/**
* Releases the underlying native object.
*
* <p>For testing only. Do not use the instance after this method is called.
* It is safe to call this method twice or more on the same instance.
* @hide
*/
public void releaseNativeObjectForTest() {
mCleaner.run();
}
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
@@ -1425,7 +1436,7 @@ public class Typeface {
public static void destroySystemFontMap() {
synchronized (SYSTEM_FONT_MAP_LOCK) {
for (Typeface typeface : sSystemFontMap.values()) {
typeface.mCleaner.run();
typeface.releaseNativeObjectForTest();
}
sSystemFontMap.clear();
if (sSystemFontMapBuffer != null) {
@@ -1433,9 +1444,25 @@ public class Typeface {
}
sSystemFontMapBuffer = null;
sSystemFontMapSharedMemory = null;
synchronized (sStyledCacheLock) {
destroyTypefaceCacheLocked(sStyledTypefaceCache);
}
synchronized (sWeightCacheLock) {
destroyTypefaceCacheLocked(sWeightTypefaceCache);
}
}
}
private static void destroyTypefaceCacheLocked(LongSparseArray<SparseArray<Typeface>> cache) {
for (int i = 0; i < cache.size(); i++) {
SparseArray<Typeface> array = cache.valueAt(i);
for (int j = 0; j < array.size(); j++) {
array.valueAt(j).releaseNativeObjectForTest();
}
}
cache.clear();
}
/** @hide */
public static void loadPreinstalledSystemFontMap() {
final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();