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

Commit f8ef7ac6 authored by Siyamed Sinir's avatar Siyamed Sinir Committed by Android (Google) Code Review
Browse files

Merge changes from topic "text_perf_sep_2017"

* changes:
  Reduce Text layout performance test combinations
  Performance test for text layout/draw
parents 1334afec d0a5206d
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
/*
 * 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<Object[]> params = new ArrayList<>();
        for (int length : new int[]{128}) {
            for (boolean cached : BOOLEANS) {
                for (TextType textType : new TextType[]{TextType.STRING,
                        TextType.SPANNABLE_BUILDER}) {
                    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*/);
    }
}
+109 −0
Original line number Diff line number Diff line
/*
 * 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<Object[]> params = new ArrayList<>();
        for (int length : new int[]{128}) {
            for (boolean boring : BOOLEANS) {
                for (boolean cached : BOOLEANS) {
                    for (TextType textType : new TextType[]{TextType.STRING,
                            TextType.SPANNABLE_BUILDER}) {
                        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();
    }
}
+138 −0
Original line number Diff line number Diff line
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();
        }
    }

}
+131 −0
Original line number Diff line number Diff line
/*
 * 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<Object[]> params = new ArrayList<>();
        for (int length : new int[]{128}) {
            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();
    }
}
+151 −0
Original line number Diff line number Diff line
/*
 * 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};

    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<Object[]> params = new ArrayList<>();
        for (int length : new int[]{128}) {
            for (boolean cached : BOOLEANS) {
                for (TextType textType : new TextType[]{TextType.STRING,
                        TextType.SPANNABLE_BUILDER}) {
                    params.add(new Object[]{textType.name(), length, textType, cached});
                }
            }
        }
        return params;
    }

    private final int mLineWidth;
    private final int mLength;
    private final TextType mTextType;
    private final boolean mCached;
    private final TextPaint mTextPaint;

    public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType,
            boolean cached) {
        mLength = length;
        mTextType = textType;
        mCached = cached;
        mTextPaint = new TextPaint();
        mTextPaint.setTextSize(10);
        mLineWidth = 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 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();
    }
}
Loading