From 9feb92f9454cabc44cdce493ee11c34a4bc9b72b Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Fri, 15 Jul 2022 06:03:07 +0000 Subject: [PATCH] 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 --- apct-tests/perftests/core/AndroidManifest.xml | 12 +++ .../TypefaceSerializationPerfTest.java | 75 ++++++++++++++++--- graphics/java/android/graphics/Typeface.java | 29 ++++++- 3 files changed, 104 insertions(+), 12 deletions(-) diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index 56fa70cfc2207..bb57161b4f6bd 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -33,6 +34,17 @@ android:exported="false" android:process=":some_provider" /> + + + + + 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 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 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 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; } } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index a2f5301e353fd..8e3acb8570f04 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1176,6 +1176,17 @@ public class Typeface { mWeight = nativeGetWeight(ni); } + /** + * Releases the underlying native object. + * + *

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> cache) { + for (int i = 0; i < cache.size(); i++) { + SparseArray 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();