Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9feb92f9 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

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
parent f99e2e7e
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
<?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"
+64 −11
Original line number Diff line number Diff line
@@ -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<>();
        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<>();
        while (state.keepRunning()) {
        // 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;
        }
    }
}
+28 −1
Original line number Diff line number Diff line
@@ -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,7 +1444,23 @@ 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 */