Merge pull request #4144 from NativeScript/property-acc-perf

Move perf tests to a separate application
This commit is contained in:
Panayot Cankov
2017-05-09 11:46:39 +03:00
committed by GitHub
8 changed files with 654 additions and 0 deletions

3
apps/app/perf-app/app.ts Normal file
View File

@ -0,0 +1,3 @@
import * as application from "tns-core-modules/application";
application.start({ moduleName: "perf-app/main-page" });

View File

@ -0,0 +1,64 @@
import {TabView} from "tns-core-modules/ui/tab-view";
import * as utils from "tns-core-modules/utils/utils";
const titles = ["Etiam lacinia", "Imperdiet ante", "A interdum", "Quisque tempus", "Sodales viverra"];
const bodies = [
"Vivamus ipsum neque, commodo rutrum finibus sit amet, cursus id sapien.",
"Duis et iaculis odio. Class aptent taciti.",
"Sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
"Etiam vitae lacinia purus. Vestibulum laoreet nibh a porta aliquet.",
"Vivamus ut eros vitae felis volutpat aliquet.",
"Aliquam fermentum mauris consequat hendrerit elementum.",
"Nam odio tortor, malesuada congue diam volutpat, placerat ullamcorper risus.",
"Curabitur eget nunc viverra justo bibendum rutrum ut nec lectus.",
"Pellentesque et lacus vel turpis suscipit posuere sed non sapien.",
"Integer eget ornare nunc. In lacinia congue sollicitudin.",
"Quisque lobortis quam in risus porttitor, ac laoreet lacus auctor."
];
const items = [];
for (var i = 0; i < 64; i++) {
items.push({
icon: "~/ui-tests-app/flexbox/icons/icon" + (1 + (i % 3)) + ".jpg",
title: titles[i % titles.length],
body: bodies[i % bodies.length],
up: (i * 991) % 100,
down: (i * 997) % 100
});
}
export function selectionChanged(args) {
clear(args);
}
let repeaterIds = ["repeaterFlexGrid", "repeaterGrid", "repeaterFlexStack", "repeaterStack"];
export function clear(args) {
repeaterIds
.map(name => args.object.page.getViewById(name))
.forEach(repeater => {
repeater.bindingContext = null;
});
args.object.page.getViewById("title").text = "---";
utils.GC();
}
export function generate(args) {
let tab: TabView = args.object.page.getViewById("tabview");
let index = tab.selectedIndex;
let repeater = args.object.page.getViewById(repeaterIds[index]);
clear(args);
setTimeout(() => {
let start = Date.now();
repeater.bindingContext = items;
setTimeout(() => {
let end = Date.now();
args.object.page.getViewById("title").text = (end - start).toString();
console.log("Duration: " + (end - start));
});
});
}

View File

@ -0,0 +1,87 @@
<Page>
<ActionBar id="actionbar">
<Label id="title" text="---" />
<ActionItem ios.systemIcon="17" android.systemIcon="ic_menu_play_clip" tap="generate" ios.position="right" />
</ActionBar>
<TabView id="tabview" selectedIndexChanged="selectionChanged">
<TabView.items>
<TabViewItem title="flex-grid">
<TabViewItem.view>
<ScrollView>
<!-- Fix this: style="flex: 1 0; height: 0;"-->
<Repeater id="repeaterFlexGrid" items="{{ $value }}">
<Repeater.itemTemplate>
<FlexboxLayout flexDirection="row" flexWrap="wrap" alignItems="center" borderWidth="0 0 1 0" borderColor="#EEEEEE">
<Image src="{{ icon }}" flexGrow="0" flexShrink="1" margin="6 4 4 4" borderWidth="1" borderColor="gray" borderRadius="16" width="32" height="32" />
<Label text="{{ title }}" flexGrow="1" flexShrink="0" width="0" margin="1 4 0 0" />
<Label text="{{ body }}" textWrap="true" width="100%" margin="4 4 6 4" fontSize="11" />
<Image src="~/ui-tests-app/flexbox/icons/thumbsdown.png" width="18" height="18" margin="4" />
<Label text="{{ up }}" flexGrow="1" flexShrink="0" width="0" fontSize="13" />
<Image src="~/ui-tests-app/flexbox/icons/thumbsup.png" width="18" height="18" margin="4" />
<Label text="{{ down }}" flexGrow="1" flexShrink="0" width="0" fontSize="13" />
</FlexboxLayout>
</Repeater.itemTemplate>
</Repeater>
</ScrollView>
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="grid">
<TabViewItem.view>
<ScrollView>
<Repeater id="repeaterGrid" items="{{ $value }}">
<Repeater.itemTemplate>
<GridLayout columns="*, *" rows="auto, auto, auto" borderWidth="0 0 1 0" borderColor="#EEEEEE">
<Image src="{{ icon }}" colSpan="2" horizontalAlignment="left" width="32" height="32" margin="6 4 4 4" borderWidth="1" borderRadius="16" borderColor="16" borderColor="gray" />
<Label text="{{ title }}" colSpan="2" margin="4 4 0 41" verticalAlignment="center" />
<Label text="{{ body }}" colSpan="2" row="1" textWrap="true" margin="4 4 6 4" fontSize="11" />
<Image src="~/thumbsdown.png" row="2" width="18" height="18" margin="4" horizontalAlignment="left" />
<Label text="{{ up }}" row="2" fontSize="13" margin="0 0 0 26" verticalAlignment="center" />
<Image src="~/thumbsup.png" row="2" col="1" width="18" height="18" margin="4" horizontalAlignment="left" />
<Label text="{{ down }}" row="2" col="1" fontSize="13" margin="0 0 0 26" verticalAlignment="center" />
</GridLayout>
</Repeater.itemTemplate>
</Repeater>
</ScrollView>
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="flex-stack">
<TabViewItem.view>
<ScrollView>
<Repeater id="repeaterFlexStack" items="{{ $value }}">
<Repeater.itemTemplate>
<FlexboxLayout flexDirection="column">
<Image src="{{ icon }}" alignSelf="center" width="32" height="32" margin="4" borderWidth="1" borderRadius="16" borderColor="16" borderColor="gray" />
<Label text="{{ title }}" margin="4" />
<Label text="{{ body }}" textWrap="true" margin="4" fontSize="11" />
<Image src="~/thumbsdown.png" width="18" height="18" margin="4" />
<Label text="{{ up }}" fontSize="13" alignSelf="center" />
<Image src="~/thumbsup.png" width="18" height="18" margin="4" />
<Label text="{{ down }}" fontSize="13" alignSelf="center" />
</FlexboxLayout>
</Repeater.itemTemplate>
</Repeater>
</ScrollView>
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="stack">
<TabViewItem.view>
<ScrollView>
<Repeater id="repeaterStack" items="{{ $value }}">
<Repeater.itemTemplate>
<StackLayout>
<Image src="{{ icon }}" horizontalAlignment="center" width="32" height="32" margin="4" borderWidth="1" borderRadius="16" borderColor="16" borderColor="gray" />
<Label text="{{ title }}" margin="4" />
<Label text="{{ body }}" textWrap="true" margin="4" fontSize="11" />
<Image src="~/thumbsdown.png" width="18" height="18" margin="4" />
<Label text="{{ up }}" fontSize="13" horizontalAlignment="center" />
<Image src="~/thumbsup.png" width="18" height="18" margin="4" />
<Label text="{{ down }}" fontSize="13" horizontalAlignment="center" />
</StackLayout>
</Repeater.itemTemplate>
</Repeater>
</ScrollView>
</TabViewItem.view>
</TabViewItem>
</TabView.items>
</TabView>
</Page>

View File

@ -0,0 +1,98 @@
import { EventData, Observable } from "tns-core-modules/data/observable";
import { WrapLayout } from "tns-core-modules/ui/layouts/wrap-layout";
import { Page } from "tns-core-modules/ui/page";
import * as buttonModule from "tns-core-modules/ui/button";
import * as colorModule from "tns-core-modules/color";
import * as platform from "tns-core-modules/platform";
import * as dialogs from "tns-core-modules/ui/dialogs";
import * as frame from "tns-core-modules/ui/frame";
export function pageLoaded(args: EventData) {
let page = <Page>args.object;
let view = require("ui/core/view");
let wrapLayout = view.getViewById(page, "wrapLayoutWithExamples");
let examples: Map<string, string> = new Map<string, string>();
examples.set("properties", "properties/main-page");
examples.set("flexbox", "flexbox/main-page");
let viewModel = new MainPageViewModel(wrapLayout, examples);
page.bindingContext = viewModel;
}
export class MainPageViewModel extends Observable {
private _exampleName: string;
private basePath: string = "";
private colors = ["#ff0000", "#0000cc", "#33cc33", "#33cc33"];
public examples: Map<string, string> = new Map<string, string>();
constructor(private panel: WrapLayout, private _examples: Map<string, string>) {
super();
// trace.enable();
// trace.setCategories(trace.categories.Test);
this.examples = _examples;
if (this.shouldLoadBtns()) {
this.loadButtons();
}
}
get exampleName(): string {
return this._exampleName;
}
set exampleName(value: string) {
if (this._exampleName !== value) {
this._exampleName = value;
this.notifyPropertyChange("exampleName", value)
}
}
public loadExample(exampleName: any) {
console.log("exampleName EXAMPLE: " + exampleName);
this.selectExample(exampleName);
}
private shouldLoadBtns(): boolean {
return this.panel.getChildrenCount() <= 0;
}
private selectExample(selectedExample: any) {
console.log(" EXAMPLE: " + selectedExample);
if (this.examples.has(selectedExample)) {
frame.topmost().navigate("perf-app/" + this.basePath + this.examples.get(selectedExample));
}
else {
dialogs.alert("Cannot find example: " + selectedExample);
}
}
private loadButtons() {
var count = 0;
this.examples.forEach((element, key) => {
var btn = new buttonModule.Button();
if (platform.isAndroid) {
btn.style.height = 25;
btn.style.fontSize = 10;
btn.style.margin = "0";
btn.style.padding = "0";
} else {
btn.style.padding = "5";
}
btn.style.color = new colorModule.Color(this.colors[count++ % 3]);
btn.on(buttonModule.Button.tapEvent, function(eventData) {
let text = btn.text;
this.loadExample(text);
}, this);
btn.text = key;
this.panel.addChild(btn)
});
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Page loaded="pageLoaded">
<ScrollView orientation="vertical" row="1">
<WrapLayout id="wrapLayoutWithExamples"/>
</ScrollView>
</Page>

View File

@ -0,0 +1,76 @@
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import { TextView } from "tns-core-modules/ui/text-view";
import { Button } from "tns-core-modules/ui/button";
import * as tests from "./tests";
const c = [10, 100, 1000, 10000, 100000];
function getStack(stack: StackLayout): StackLayout {
let p = new StackLayout();
stack.removeChildren();
stack.addChild(p);
return p;
}
let runner;
export function onNavigatingFrom() {
clearInterval(runner);
}
export function onTap3(args) {
let btn = <Button>args.object;
const p = btn.page.getViewById<StackLayout>("placeholder");
btn.text = "Start tests...";
let result = btn.page.getViewById<TextView>("result");
result.text = "";
function track(line: string) {
console.log(line);
result.fontSize = 10;
result.text += line + "\n";
}
let text = "Count";
c.forEach(e => {
text += `\t${e}`;
});
track(text);
let tasks = [
() => track(tests.setBackgroundColor(c)),
() => track(tests.setBackgroundColor(c, getStack(p))),
() => track(tests.setBindingContext(c)),
() => track(tests.setBindingContext(c, getStack(p))),
() => track(tests.setBindingContextWithParents(c, getStack(p))),
() => track(tests.setBindingContextWithParentsBound(c, getStack(p))),
() => track(tests.setBorderWidths(c)),
() => track(tests.setBorderWidths(c, getStack(p))),
() => track(tests.setFontSize(c)),
() => track(tests.setFontSize(c, getStack(p))),
() => track(tests.setFontSizeWithParents(c, getStack(p))),
() => track(tests.setFontWeight(c)),
() => track(tests.setFontWeight(c, getStack(p))),
() => track(tests.setFontWeightWithParents(c, getStack(p))),
() => track(tests.setColor(c)),
() => track(tests.setColor(c, getStack(p))),
() => track(tests.setColorWithParents(c, getStack(p))),
() => track(tests.setText(c)),
() => track(tests.setText(c, getStack(p))),
() => track(tests.addRemove(c, getStack(p))),
() => track("Complete!")
];
let i = 0;
runner = setInterval(nextTask, 1);
function nextTask() {
if (i < tasks.length) {
tasks[i]();
i++;
} else {
clearInterval(runner);
}
}
}

View File

@ -0,0 +1,7 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingFrom="onNavigatingFrom">
<GridLayout rows="30,100,*">
<Button text="Start test..." tap="onTap3" style="font-size:8" />
<StackLayout id="placeholder" row="1"/>
<TextView id="result" row="2" />
</GridLayout>
</Page>

View File

@ -0,0 +1,313 @@
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import { Label } from "tns-core-modules/ui/label";
const average = 3;
const noValue = "noValue";
const colors = ['red', 'green'];
export function addRemove(counts: Array<number>, parent: LayoutBase): string {
let result = `addRemove`;
counts.forEach((count) => {
if (count > 10000) {
result += setResultTime(noValue);
return;
}
const lbl = new Label();
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
parent.addChild(lbl);
parent.removeChild(lbl);
}
});
result += setResultTime(time);
});
return result;
}
export function setText(counts: Array<number>, parent?: LayoutBase): string {
let result = `setText ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
const lbl = setup(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
lbl.text = colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setBackgroundColor(counts: Array<number>, parent?: LayoutBase): string {
let result = `setBackgroundColor ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
if (parent && count > 10000) {
result += setResultTime(noValue);
return;
}
const lbl = setup(parent);
const style = lbl.style;
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.backgroundColor = <any>colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
const borders = [1, 2, 3];
export function setBorderWidths(counts: Array<number>, parent?: LayoutBase): string {
let result = `setBorderWidths ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
if (count > 10000 && parent) {
result += setResultTime(noValue);
return;
}
const lbl = setup(parent);
const style = lbl.style;
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.borderLeftWidth = borders[i % 3];
style.borderTopWidth = borders[i % 3];
style.borderRightWidth = borders[i % 3];
style.borderBottomWidth = borders[i % 3];
}
});
result += setResultTime(time);
});
return result;
}
export function setColor(counts: Array<number>, parent?: LayoutBase): string {
let result = `setColor ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
const lbl = setup(parent);
const style = lbl.style;
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.color = <any>colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setColorWithParents(counts: Array<number>, parent: LayoutBase): string {
let result = `setColorWithParents`;
const style = parent.style;
counts.forEach((count) => {
if (count > 10000) {
result += setResultTime(noValue);
return;
}
setupParents(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.color = <any>colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
const fontSizes = [10, 20];
export function setFontSize(counts: Array<number>, parent?: LayoutBase): string {
let result = `setFontSize ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
const lbl = setup(parent);
const style = lbl.style;
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.fontSize = fontSizes[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setFontSizeWithParents(counts: Array<number>, parent: LayoutBase): string {
let result = `setFontSizeWithParents`;
const style = parent.style;
counts.forEach((count) => {
if (count > 1000) {
result += setResultTime(noValue);
return;
}
setupParents(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.fontSize = fontSizes[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setFontWeight(counts: Array<number>, parent?: LayoutBase): string {
let result = `setFontWeight ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
const lbl = setup(parent);
const style = lbl.style;
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.fontWeight = i % 2 === 0 ? 'bold' : 'normal';
}
});
result += setResultTime(time);
});
return result;
}
export function setFontWeightWithParents(counts: Array<number>, parent: LayoutBase): string {
let result = `setFontWeightWithParents`;
const style = parent.style;
counts.forEach((count) => {
if (count > 1000) {
result += setResultTime(noValue);
return;
}
setupParents(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
style.fontWeight = i % 2 === 0 ? 'bold' : 'normal'
}
});
result += setResultTime(time);
});
return result;
}
export function setBindingContext(counts: Array<number>, parent?: LayoutBase): string {
let result = `setBindingContext ${parent ? 'with nativeView' : ''}`;
counts.forEach((count) => {
const lbl = setup(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
lbl.bindingContext = colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setBindingContextWithParents(counts: Array<number>, parent: LayoutBase): string {
let result = `setBindingContextWithParents`;
counts.forEach((count) => {
if (count > 10000) {
result += setResultTime(noValue);
return;
}
setupParents(parent);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
parent.bindingContext = colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
export function setBindingContextWithParentsBound(counts: Array<number>, parent: LayoutBase): string {
let result = `setBindingContextWithParentsBound`;
counts.forEach((count) => {
if (count > 1000) {
result += setResultTime(noValue);
return;
}
setupParents(parent, true);
const time = executeTest(() => {
for (let i = 0; i < count; i++) {
parent.bindingContext = colors[i % 2];
}
});
result += setResultTime(time);
});
return result;
}
function setupParents(parent: LayoutBase, bindToContext: boolean = false): void {
for (let i = 0; i < 3; i++) {
let stack = new StackLayout();
parent.addChild(stack)
for (let j = 0; j < 3; j++) {
let innerStack = new StackLayout();
stack.addChild(innerStack);
for (let k = 0; k < 3; k++) {
const lbl = new Label();
if (bindToContext) {
lbl.bind({ sourceProperty: "$value", targetProperty: "text" });
}
innerStack.addChild(lbl);
}
}
}
}
function setup(parent?: LayoutBase): Label {
let lbl = new Label();
if (parent) {
parent.addChild(lbl);
} else {
(<any>lbl)._ios = (<any>lbl).nativeView = undefined;
}
return lbl;
}
function time(): number {
if (global.android) {
return (<any>global).java.lang.System.nanoTime() / 1000000;
} else {
return (<any>global).CACurrentMediaTime() * 1000;
}
}
function executeTest(func: Function): string {
let total = 0;
for (let i = 0; i < average; i++) {
const start = time();
func();
const end = time();
const duration = end - start;
total += duration;
}
const avg = total / average;
const res = `${avg.toFixed(2)}`;
return res;
}
function setResultTime(time: string) {
return `\t${time}`;
}