From af479b8c9743a1536d46f0985294c8bc0823a338 Mon Sep 17 00:00:00 2001 From: Siyamed Sinir Date: Tue, 26 Sep 2017 22:52:07 -0700 Subject: [PATCH 1/2] Performance test for text layout/draw Test: bit CorePerfTests:android.text.PaintMeasureDrawPerfTest Test: bit CorePerfTests:android.text.BoringLayoutIsBoringPerfTest Test: bit CorePerfTests:android.text.BoringLayoutCreateDrawPerfTest Test: bit CorePerfTests:android.text.StaticLayoutCreateDrawPerfTest Test: bit CorePerfTests:android.text.TextViewSetTextMeasurePerfTest Bug: 67112217 Change-Id: Ibb55431fb82a4fccba39c2bc250c83560dda0259 --- .../text/BoringLayoutCreateDrawPerfTest.java | 149 ++++++++++++++++ .../text/BoringLayoutIsBoringPerfTest.java | 108 ++++++++++++ .../text/NonEditableTextGenerator.java | 138 +++++++++++++++ .../text/PaintMeasureDrawPerfTest.java | 131 ++++++++++++++ .../text/StaticLayoutCreateDrawPerfTest.java | 160 ++++++++++++++++++ .../text/TextViewSetTextMeasurePerfTest.java | 160 ++++++++++++++++++ 6 files changed, 846 insertions(+) create mode 100644 apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java create mode 100644 apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java create mode 100644 apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java create mode 100644 apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java create mode 100644 apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java create mode 100644 apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java new file mode 100644 index 0000000000000..dc4669b5e867e --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.text; + +import static android.text.Layout.Alignment.ALIGN_NORMAL; + +import android.graphics.Canvas; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.text.NonEditableTextGenerator.TextType; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +/** + * Performance test for {@link BoringLayout} create and draw. + */ +@LargeTest +@RunWith(Parameterized.class) +public class BoringLayoutCreateDrawPerfTest { + + private static final boolean[] BOOLEANS = new boolean[]{false, true}; + private static final float SPACING_ADD = 10f; + private static final float SPACING_MULT = 1.5f; + + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") + public static Collection cases() { + final List params = new ArrayList<>(); + for (int length : new int[]{32, 64, 128, 256, 512}) { + for (boolean cached : BOOLEANS) { + for (TextType textType : TextType.values()) { + params.add(new Object[]{textType.name(), length, textType, cached}); + } + } + } + return params; + } + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private final int mLength; + private final TextType mTextType; + private final boolean mCached; + private final TextPaint mTextPaint; + + public BoringLayoutCreateDrawPerfTest(String label, int length, TextType textType, + boolean cached) { + mLength = length; + mCached = cached; + mTextType = textType; + mTextPaint = new TextPaint(); + mTextPaint.setTextSize(10); + } + + /** + * Measures the creation time for {@link BoringLayout}. + */ + @Test + public void timeCreate() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final CharSequence text = createRandomText(); + // isBoring result is calculated in another test, we want to measure only the + // create time for Boring without isBoring check. Therefore it is calculated here. + final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint); + if (mCached) createLayout(text, metrics); + state.resumeTiming(); + + while (state.keepRunning()) { + state.pauseTiming(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + createLayout(text, metrics); + } + } + + /** + * Measures the draw time for {@link BoringLayout} or {@link StaticLayout}. + */ + @Test + public void timeDraw() throws Throwable { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final RenderNode node = RenderNode.create("benchmark", null); + final CharSequence text = createRandomText(); + final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint); + final Layout layout = createLayout(text, metrics); + state.resumeTiming(); + + while (state.keepRunning()) { + + state.pauseTiming(); + final DisplayListCanvas canvas = node.start(1200, 200); + final int save = canvas.save(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(canvas); + + state.pauseTiming(); + canvas.restoreToCount(save); + node.end(canvas); + state.resumeTiming(); + } + } + + private CharSequence createRandomText() { + return new NonEditableTextGenerator(new Random(0)) + .setSequenceLength(mLength) + .setCreateBoring(true) + .setTextType(mTextType) + .build(); + } + + private Layout createLayout(CharSequence text, + BoringLayout.Metrics metrics) { + return BoringLayout.make(text, mTextPaint, Integer.MAX_VALUE /*width*/, + ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, metrics, true /*includePad*/); + } +} diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java new file mode 100644 index 0000000000000..f95ccd5ba1652 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.text; + +import android.graphics.Canvas; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.text.NonEditableTextGenerator.TextType; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +/** + * Performance test for {@link BoringLayout#isBoring(CharSequence, TextPaint)}. + */ +@LargeTest +@RunWith(Parameterized.class) +public class BoringLayoutIsBoringPerfTest { + + private static final boolean[] BOOLEANS = new boolean[]{false, true}; + + @Parameterized.Parameters(name = "cached={4},{1} chars,{0}") + public static Collection cases() { + final List params = new ArrayList<>(); + for (int length : new int[]{32, 64, 128, 256, 512}) { + for (boolean boring : BOOLEANS) { + for (boolean cached : BOOLEANS) { + for (TextType textType : TextType.values()) { + params.add(new Object[]{ + (boring ? "Boring" : "NotBoring") + "," + textType.name(), + length, boring, textType, cached}); + } + } + } + } + return params; + } + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private final int mLength; + private final TextType mTextType; + private final boolean mCreateBoring; + private final boolean mCached; + private final TextPaint mTextPaint; + + public BoringLayoutIsBoringPerfTest(String label, int length, boolean boring, TextType textType, + boolean cached) { + mLength = length; + mCreateBoring = boring; + mCached = cached; + mTextType = textType; + mTextPaint = new TextPaint(); + mTextPaint.setTextSize(10); + } + + /** + * Measure the time for the {@link BoringLayout#isBoring(CharSequence, TextPaint)}. + */ + @Test + public void timeIsBoring() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final CharSequence text = createRandomText(); + if (mCached) BoringLayout.isBoring(text, mTextPaint); + state.resumeTiming(); + + while (state.keepRunning()) { + state.pauseTiming(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + BoringLayout.isBoring(text, mTextPaint); + } + } + + private CharSequence createRandomText() { + return new NonEditableTextGenerator(new Random(0)) + .setSequenceLength(mLength) + .setCreateBoring(mCreateBoring) + .setTextType(mTextType) + .build(); + } +} diff --git a/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java b/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java new file mode 100644 index 0000000000000..7c0cf0eebc790 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java @@ -0,0 +1,138 @@ +package android.text; + +import static android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE; + +import android.text.style.BulletSpan; + +import java.util.Random; + +/** + * + */ +public class NonEditableTextGenerator { + + enum TextType { + STRING, + SPANNED, + SPANNABLE_BUILDER + } + + private boolean mCreateBoring; + private TextType mTextType; + private int mSequenceLength; + private final Random mRandom; + + public NonEditableTextGenerator(Random random) { + mRandom = random; + } + + public NonEditableTextGenerator setCreateBoring(boolean createBoring) { + mCreateBoring = createBoring; + return this; + } + + public NonEditableTextGenerator setTextType(TextType textType) { + mTextType = textType; + return this; + } + + public NonEditableTextGenerator setSequenceLength(int sequenceLength) { + mSequenceLength = sequenceLength; + return this; + } + + /** + * Sample charSequence generated: + * NRjPzjvUadHmH ExoEoTqfx pCLw qtndsqfpk AqajVCbgjGZ igIeC dfnXRgA + */ + public CharSequence build() { + final RandomCharSequenceGenerator sequenceGenerator = new RandomCharSequenceGenerator( + mRandom); + if (mSequenceLength > 0) { + sequenceGenerator.setSequenceLength(mSequenceLength); + } + + final CharSequence charSequence = sequenceGenerator.buildLatinSequence(); + + switch (mTextType) { + case SPANNED: + case SPANNABLE_BUILDER: + return createSpannable(charSequence); + case STRING: + default: + return createString(charSequence); + } + } + + private Spannable createSpannable(CharSequence charSequence) { + final Spannable spannable = (mTextType == TextType.SPANNABLE_BUILDER) ? + new SpannableStringBuilder(charSequence) : new SpannableString(charSequence); + + if (!mCreateBoring) { + // add a paragraph style to make it non boring + spannable.setSpan(new BulletSpan(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE); + } + + spannable.setSpan(new Object(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE); + spannable.setSpan(new Object(), 0, 1, SPAN_INCLUSIVE_INCLUSIVE); + + return spannable; + } + + private String createString(CharSequence charSequence) { + if (mCreateBoring) { + return charSequence.toString(); + } else { + // BoringLayout checks to see if there is a surrogate pair and if so tells that + // the charSequence is not suitable for boring. Add an emoji to make it non boring. + // Emoji is added instead of RTL, since emoji stays in the same run and is a more + // common case. + return charSequence.toString() + "\uD83D\uDC68\uD83C\uDFFF"; + } + } + + public static class RandomCharSequenceGenerator { + + private static final int DEFAULT_MIN_WORD_LENGTH = 3; + private static final int DEFAULT_MAX_WORD_LENGTH = 15; + private static final int DEFAULT_SEQUENCE_LENGTH = 256; + + private int mMinWordLength = DEFAULT_MIN_WORD_LENGTH; + private int mMaxWordLength = DEFAULT_MAX_WORD_LENGTH; + private int mSequenceLength = DEFAULT_SEQUENCE_LENGTH; + private final Random mRandom; + + public RandomCharSequenceGenerator(Random random) { + mRandom = random; + } + + public RandomCharSequenceGenerator setSequenceLength(int sequenceLength) { + mSequenceLength = sequenceLength; + return this; + } + + public CharSequence buildLatinSequence() { + final StringBuilder result = new StringBuilder(); + while (result.length() < mSequenceLength) { + // add random word + result.append(buildLatinWord()); + result.append(' '); + } + return result.substring(0, mSequenceLength); + } + + public CharSequence buildLatinWord() { + final StringBuilder result = new StringBuilder(); + // create a random length that is (mMinWordLength + random amount of chars) where + // total size is less than mMaxWordLength + final int length = mRandom.nextInt(mMaxWordLength - mMinWordLength) + mMinWordLength; + while (result.length() < length) { + // add random letter + int base = mRandom.nextInt(2) == 0 ? 'A' : 'a'; + result.append(Character.toChars(mRandom.nextInt(26) + base)); + } + return result.toString(); + } + } + +} diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java new file mode 100644 index 0000000000000..2b8ea0617217b --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.text; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +/** + * Performance test for single line measure and draw using {@link Paint} and {@link Canvas}. + */ +@LargeTest +@RunWith(Parameterized.class) +public class PaintMeasureDrawPerfTest { + + private static final boolean[] BOOLEANS = new boolean[]{false, true}; + + @Parameterized.Parameters(name = "cached={1},{0} chars") + public static Collection cases() { + final List params = new ArrayList<>(); + for (int length : new int[]{32, 64, 128, 256, 512}) { + for (boolean cached : BOOLEANS) { + params.add(new Object[]{length, cached}); + } + } + return params; + } + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private final int mLength; + private final boolean mCached; + private final TextPaint mTextPaint; + + + public PaintMeasureDrawPerfTest(int length, boolean cached) { + mLength = length; + mCached = cached; + mTextPaint = new TextPaint(); + mTextPaint.setTextSize(10); + } + + /** + * Measure the time for {@link Paint#measureText(String)} + */ + @Test + public void timeMeasure() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final String text = createRandomText(); + if (mCached) mTextPaint.measureText(text); + state.resumeTiming(); + + while (state.keepRunning()) { + state.pauseTiming(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + mTextPaint.measureText(text); + } + } + + /** + * Measures the time for {@link Canvas#drawText(String, float, float, Paint)} + */ + @Test + public void timeDraw() throws Throwable { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final RenderNode node = RenderNode.create("benchmark", null); + final String text = createRandomText(); + if (mCached) mTextPaint.measureText(text); + state.resumeTiming(); + + while (state.keepRunning()) { + + state.pauseTiming(); + final DisplayListCanvas canvas = node.start(1200, 200); + final int save = canvas.save(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + canvas.drawText(text, 0 /*x*/, 100 /*y*/, mTextPaint); + + state.pauseTiming(); + canvas.restoreToCount(save); + node.end(canvas); + state.resumeTiming(); + } + } + + private String createRandomText() { + return (String) new NonEditableTextGenerator(new Random(0)) + .setSequenceLength(mLength) + .setCreateBoring(true) + .setTextType(NonEditableTextGenerator.TextType.STRING) + .build(); + } +} diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java new file mode 100644 index 0000000000000..e09f1a1df2e8e --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.text; + +import static android.text.Layout.Alignment.ALIGN_NORMAL; + +import android.graphics.Canvas; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.text.NonEditableTextGenerator.TextType; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +/** + * Performance test for multi line, single style {@link StaticLayout} creation/draw. + */ +@LargeTest +@RunWith(Parameterized.class) +public class StaticLayoutCreateDrawPerfTest { + + private static final boolean[] BOOLEANS = new boolean[]{false, true}; + + // keep it one char longer than 32 so that 32 chars can fit into one line + private static final int LINE_LENGTH = 33; + private static final float SPACING_ADD = 10f; + private static final float SPACING_MULT = 1.5f; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") + public static Collection cases() { + final List params = new ArrayList<>(); + for (int length : new int[]{32, 64, 128, 256, 512}) { + for (boolean cached : BOOLEANS) { + for (boolean multiLine : BOOLEANS) { + for (TextType textType : TextType.values()) { + params.add(new Object[]{ + (multiLine ? "MultiLine" : "SingleLine") + "," + textType.name(), + length, textType, cached, multiLine}); + } + } + } + } + return params; + } + + private final int mLineWidth; + private final int mLength; + private final TextType mTextType; + private final boolean mCached; + private final boolean mMultiLine; + private final TextPaint mTextPaint; + + public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType, + boolean cached, boolean multiLine) { + mLength = length; + mTextType = textType; + mCached = cached; + mMultiLine = multiLine; + mTextPaint = new TextPaint(); + mTextPaint.setTextSize(10); + final CharSequence text = createRandomText(LINE_LENGTH); + mLineWidth = mMultiLine ? (int) mTextPaint.measureText(text.toString()) : Integer.MAX_VALUE; + } + + /** + * Measures the creation time for a multi line {@link StaticLayout}. + */ + @Test + public void timeCreate() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final CharSequence text = createRandomText(mLength); + createLayout(text); + state.resumeTiming(); + + while (state.keepRunning()) { + state.pauseTiming(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + createLayout(text); + } + } + + /** + * Measures the draw time for a multi line {@link StaticLayout}. + */ + @Test + public void timeDraw() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final RenderNode node = RenderNode.create("benchmark", null); + final Random random = new Random(); + final CharSequence text = createRandomText(mLength); + final Layout layout = createLayout(text); + state.resumeTiming(); + + while (state.keepRunning()) { + + state.pauseTiming(); + final DisplayListCanvas canvas = node.start(1200, 200); + int save = canvas.save(); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(canvas); + + state.pauseTiming(); + canvas.restoreToCount(save); + node.end(canvas); + state.resumeTiming(); + } + } + + private Layout createLayout(CharSequence text) { + return StaticLayout.Builder.obtain(text, 0 /*start*/, text.length() /*end*/, mTextPaint, + mLineWidth) + .setAlignment(ALIGN_NORMAL) + .setIncludePad(true) + .setLineSpacing(SPACING_ADD, SPACING_MULT) + .build(); + } + + private CharSequence createRandomText(int length) { + return new NonEditableTextGenerator(new Random(0)) + .setSequenceLength(length) + .setTextType(mTextType) + .build(); + } +} diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java new file mode 100644 index 0000000000000..e259de7194c71 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.text; + +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.UNSPECIFIED; + +import android.graphics.Canvas; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.text.NonEditableTextGenerator.TextType; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.widget.TextView; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +/** + * Performance test for multi line, single style {@link StaticLayout} creation/draw. + */ +@LargeTest +@RunWith(Parameterized.class) +public class TextViewSetTextMeasurePerfTest { + + private static final boolean[] BOOLEANS = new boolean[]{false, true}; + + // keep it one char longer than 32 so that 32 chars can fit into one line + private static final int LINE_LENGTH = 33; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") + public static Collection cases() { + final List params = new ArrayList<>(); + for (int length : new int[]{32, 64, 128, 256, 512}) { + for (boolean cached : BOOLEANS) { + for (boolean multiLine : BOOLEANS) { + for (TextType textType : TextType.values()) { + params.add(new Object[]{ + (multiLine ? "MultiLine" : "SingleLine") + "," + textType.name(), + length, textType, cached, multiLine}); + } + } + } + } + return params; + } + + private final int mLineWidth; + private final int mLength; + private final TextType mTextType; + private final boolean mCached; + private final boolean mMultiLine; + private final TextPaint mTextPaint; + + public TextViewSetTextMeasurePerfTest(String label, int length, TextType textType, + boolean cached, boolean multiLine) { + mLength = length; + mTextType = textType; + mCached = cached; + mMultiLine = multiLine; + mTextPaint = new TextPaint(); + mTextPaint.setTextSize(10); + final CharSequence text = createRandomText(LINE_LENGTH); + mLineWidth = mMultiLine ? (int) mTextPaint.measureText(text.toString()) : Integer.MAX_VALUE; + } + + /** + * Measures the time to setText and measure for a {@link TextView}. + */ + @Test + public void timeCreate() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final CharSequence text = createRandomText(mLength); + final TextView textView = new TextView(InstrumentationRegistry.getTargetContext()); + textView.setText(text); + state.resumeTiming(); + + while (state.keepRunning()) { + state.pauseTiming(); + textView.setTextLocale(Locale.UK); + textView.setTextLocale(Locale.US); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + textView.setText(text); + textView.measure(AT_MOST | mLineWidth, UNSPECIFIED); + } + } + + /** + * Measures the time to draw for a {@link TextView}. + */ + @Test + public void timeDraw() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + state.pauseTiming(); + Canvas.freeTextLayoutCaches(); + final RenderNode node = RenderNode.create("benchmark", null); + final CharSequence text = createRandomText(mLength); + final TextView textView = new TextView(InstrumentationRegistry.getTargetContext()); + textView.setText(text); + state.resumeTiming(); + + while (state.keepRunning()) { + + state.pauseTiming(); + final DisplayListCanvas canvas = node.start(1200, 200); + int save = canvas.save(); + textView.setTextLocale(Locale.UK); + textView.setTextLocale(Locale.US); + if (!mCached) Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + textView.draw(canvas); + + state.pauseTiming(); + canvas.restoreToCount(save); + node.end(canvas); + state.resumeTiming(); + } + } + + private CharSequence createRandomText(int length) { + return new NonEditableTextGenerator(new Random(0)) + .setSequenceLength(length) + .setCreateBoring(false) + .setTextType(mTextType) + .build(); + } +} From d0a5206dd8130611567acab92206eeecc222a65e Mon Sep 17 00:00:00 2001 From: Siyamed Sinir Date: Mon, 2 Oct 2017 15:26:37 -0700 Subject: [PATCH 2/2] Reduce Text layout performance test combinations We have measured and identified important combinations for layout performance tests. In order to reduce the number of tests logged, this CL removed some of the parameterized tests configurations. Test: bit CorePerfTests:android.text.PaintMeasureDrawPerfTest Test: bit CorePerfTests:android.text.BoringLayoutIsBoringPerfTest Test: bit CorePerfTests:android.text.BoringLayoutCreateDrawPerfTest Test: bit CorePerfTests:android.text.StaticLayoutCreateDrawPerfTest Test: bit CorePerfTests:android.text.TextViewSetTextMeasurePerfTest Bug: 67112217 Change-Id: I92a08f679a185595a519465450881a814f2b4feb --- .../text/BoringLayoutCreateDrawPerfTest.java | 5 +++-- .../text/BoringLayoutIsBoringPerfTest.java | 5 +++-- .../text/PaintMeasureDrawPerfTest.java | 2 +- .../text/StaticLayoutCreateDrawPerfTest.java | 21 ++++++------------- .../text/TextViewSetTextMeasurePerfTest.java | 21 ++++++------------- 5 files changed, 19 insertions(+), 35 deletions(-) diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java index dc4669b5e867e..47dd257b06b5c 100644 --- a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java @@ -49,9 +49,10 @@ public class BoringLayoutCreateDrawPerfTest { @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List params = new ArrayList<>(); - for (int length : new int[]{32, 64, 128, 256, 512}) { + for (int length : new int[]{128}) { for (boolean cached : BOOLEANS) { - for (TextType textType : TextType.values()) { + for (TextType textType : new TextType[]{TextType.STRING, + TextType.SPANNABLE_BUILDER}) { params.add(new Object[]{textType.name(), length, textType, cached}); } } diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java index f95ccd5ba1652..34de65de2627c 100644 --- a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java @@ -43,10 +43,11 @@ public class BoringLayoutIsBoringPerfTest { @Parameterized.Parameters(name = "cached={4},{1} chars,{0}") public static Collection cases() { final List params = new ArrayList<>(); - for (int length : new int[]{32, 64, 128, 256, 512}) { + for (int length : new int[]{128}) { for (boolean boring : BOOLEANS) { for (boolean cached : BOOLEANS) { - for (TextType textType : TextType.values()) { + for (TextType textType : new TextType[]{TextType.STRING, + TextType.SPANNABLE_BUILDER}) { params.add(new Object[]{ (boring ? "Boring" : "NotBoring") + "," + textType.name(), length, boring, textType, cached}); diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java index 2b8ea0617217b..00b60add5b15d 100644 --- a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java @@ -45,7 +45,7 @@ public class PaintMeasureDrawPerfTest { @Parameterized.Parameters(name = "cached={1},{0} chars") public static Collection cases() { final List params = new ArrayList<>(); - for (int length : new int[]{32, 64, 128, 256, 512}) { + for (int length : new int[]{128}) { for (boolean cached : BOOLEANS) { params.add(new Object[]{length, cached}); } diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java index e09f1a1df2e8e..356e2e0dab3c9 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java @@ -44,8 +44,6 @@ public class StaticLayoutCreateDrawPerfTest { private static final boolean[] BOOLEANS = new boolean[]{false, true}; - // keep it one char longer than 32 so that 32 chars can fit into one line - private static final int LINE_LENGTH = 33; private static final float SPACING_ADD = 10f; private static final float SPACING_MULT = 1.5f; @@ -55,14 +53,11 @@ public class StaticLayoutCreateDrawPerfTest { @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List params = new ArrayList<>(); - for (int length : new int[]{32, 64, 128, 256, 512}) { + for (int length : new int[]{128}) { for (boolean cached : BOOLEANS) { - for (boolean multiLine : BOOLEANS) { - for (TextType textType : TextType.values()) { - params.add(new Object[]{ - (multiLine ? "MultiLine" : "SingleLine") + "," + textType.name(), - length, textType, cached, multiLine}); - } + for (TextType textType : new TextType[]{TextType.STRING, + TextType.SPANNABLE_BUILDER}) { + params.add(new Object[]{textType.name(), length, textType, cached}); } } } @@ -73,19 +68,16 @@ public class StaticLayoutCreateDrawPerfTest { private final int mLength; private final TextType mTextType; private final boolean mCached; - private final boolean mMultiLine; private final TextPaint mTextPaint; public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType, - boolean cached, boolean multiLine) { + boolean cached) { mLength = length; mTextType = textType; mCached = cached; - mMultiLine = multiLine; mTextPaint = new TextPaint(); mTextPaint.setTextSize(10); - final CharSequence text = createRandomText(LINE_LENGTH); - mLineWidth = mMultiLine ? (int) mTextPaint.measureText(text.toString()) : Integer.MAX_VALUE; + mLineWidth = Integer.MAX_VALUE; } /** @@ -120,7 +112,6 @@ public class StaticLayoutCreateDrawPerfTest { state.pauseTiming(); Canvas.freeTextLayoutCaches(); final RenderNode node = RenderNode.create("benchmark", null); - final Random random = new Random(); final CharSequence text = createRandomText(mLength); final Layout layout = createLayout(text); state.resumeTiming(); diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java index e259de7194c71..a2bf33e1f607d 100644 --- a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java +++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java @@ -48,23 +48,17 @@ public class TextViewSetTextMeasurePerfTest { private static final boolean[] BOOLEANS = new boolean[]{false, true}; - // keep it one char longer than 32 so that 32 chars can fit into one line - private static final int LINE_LENGTH = 33; - @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List params = new ArrayList<>(); - for (int length : new int[]{32, 64, 128, 256, 512}) { + for (int length : new int[]{128}) { for (boolean cached : BOOLEANS) { - for (boolean multiLine : BOOLEANS) { - for (TextType textType : TextType.values()) { - params.add(new Object[]{ - (multiLine ? "MultiLine" : "SingleLine") + "," + textType.name(), - length, textType, cached, multiLine}); - } + for (TextType textType : new TextType[]{TextType.STRING, + TextType.SPANNABLE_BUILDER}) { + params.add(new Object[]{textType.name(), length, textType, cached}); } } } @@ -75,19 +69,16 @@ public class TextViewSetTextMeasurePerfTest { private final int mLength; private final TextType mTextType; private final boolean mCached; - private final boolean mMultiLine; private final TextPaint mTextPaint; public TextViewSetTextMeasurePerfTest(String label, int length, TextType textType, - boolean cached, boolean multiLine) { + boolean cached) { mLength = length; mTextType = textType; mCached = cached; - mMultiLine = multiLine; mTextPaint = new TextPaint(); mTextPaint.setTextSize(10); - final CharSequence text = createRandomText(LINE_LENGTH); - mLineWidth = mMultiLine ? (int) mTextPaint.measureText(text.toString()) : Integer.MAX_VALUE; + mLineWidth = Integer.MAX_VALUE; } /**