Title: [225324] trunk/PerformanceTests
Revision
225324
Author
[email protected]
Date
2017-11-30 04:30:25 -0800 (Thu, 30 Nov 2017)

Log Message

Add StyleBench
https://bugs.webkit.org/show_bug.cgi?id=180140

Reviewed by Simon Fraser and Joseph Pecoraro.

StyleBench tests performance of the CSS style resolution and style invalidation. Each test run
creates a large document and a large stylesheet using varying settings. It then applies
a series of mutations to the document and measures the time to update the style and rendering.
The resulting layout is simple, most of the pressure is on selector matching.

StyleBench uses Speedometer framework for UI and measurements. For profiling purposes, it can also
be run locally by opening style-bench.html directly.

There are currently four subtests:

- child and descendant combinators only (all other tests have these too).
- sibling combinators: '~' and '+'
- positional pseudo classes: :nth-child and similar
- ::before and ::after pseudo elements

The measured DOM mutations are:

- add classes
- remove classes
- add leaf elements
- remove leaf elements

* StyleBench: Added.
* StyleBench/InteractiveRunner.html: Added.

    Copied and customized from Speedometer.

* StyleBench/index.html: Added.

    Copied and customized from Speedometer.

* StyleBench/resources: Added.
* StyleBench/resources/style-bench.html: Added.
* StyleBench/resources/style-bench.js: Added.

    The test class.

(Random):
(Random.prototype.get next):
(Random.prototype.chance):
(Random.prototype.number):
(nextAnimationFrame):
(defaultConfiguration):
(descendantCombinatorConfiguration):
(siblingCombinatorConfiguration):
(pseudoClassConfiguration):
(beforeAndAfterConfiguration):
(predefinedConfigurations):

    Four predefined configurations.

(prototype.randomElementName):
(prototype.randomCombinator):
(prototype.randomPseudoClass):
(prototype.makeSimpleSelector):
(prototype.makeSelector):
(prototype.get randomColorComponent):
(prototype.makeDeclaration):
(prototype.makeRule):
(prototype.makeStylesheet):
(prototype.makeStyle):
(prototype.makeElement):
(prototype.makeTreeWithDepth):
(prototype.makeTree):
(prototype.updateCachedTestElements):
(prototype.randomTreeElement):
(prototype.addClasses):
(prototype.removeClasses):
(prototype.addLeafElements):
(prototype.removeLeafElements):
(prototype.async.runForever):
* StyleBench/resources/tests.js: Added.
(makeSteps):
(makeSuite):

    Generates Speedometer Suites.

Modified Paths

Added Paths

Diff

Modified: trunk/PerformanceTests/ChangeLog (225323 => 225324)


--- trunk/PerformanceTests/ChangeLog	2017-11-30 12:00:56 UTC (rev 225323)
+++ trunk/PerformanceTests/ChangeLog	2017-11-30 12:30:25 UTC (rev 225324)
@@ -1,3 +1,87 @@
+2017-11-30  Antti Koivisto  <[email protected]>
+
+        Add StyleBench
+        https://bugs.webkit.org/show_bug.cgi?id=180140
+
+        Reviewed by Simon Fraser and Joseph Pecoraro.
+
+        StyleBench tests performance of the CSS style resolution and style invalidation. Each test run
+        creates a large document and a large stylesheet using varying settings. It then applies
+        a series of mutations to the document and measures the time to update the style and rendering.
+        The resulting layout is simple, most of the pressure is on selector matching.
+
+        StyleBench uses Speedometer framework for UI and measurements. For profiling purposes, it can also
+        be run locally by opening style-bench.html directly.
+
+        There are currently four subtests:
+
+        - child and descendant combinators only (all other tests have these too).
+        - sibling combinators: '~' and '+'
+        - positional pseudo classes: :nth-child and similar
+        - ::before and ::after pseudo elements
+
+        The measured DOM mutations are:
+
+        - add classes
+        - remove classes
+        - add leaf elements
+        - remove leaf elements
+
+        * StyleBench: Added.
+        * StyleBench/InteractiveRunner.html: Added.
+
+            Copied and customized from Speedometer.
+
+        * StyleBench/index.html: Added.
+
+            Copied and customized from Speedometer.
+
+        * StyleBench/resources: Added.
+        * StyleBench/resources/style-bench.html: Added.
+        * StyleBench/resources/style-bench.js: Added.
+
+            The test class.
+
+        (Random):
+        (Random.prototype.get next):
+        (Random.prototype.chance):
+        (Random.prototype.number):
+        (nextAnimationFrame):
+        (defaultConfiguration):
+        (descendantCombinatorConfiguration):
+        (siblingCombinatorConfiguration):
+        (pseudoClassConfiguration):
+        (beforeAndAfterConfiguration):
+        (predefinedConfigurations):
+
+            Four predefined configurations.
+
+        (prototype.randomElementName):
+        (prototype.randomCombinator):
+        (prototype.randomPseudoClass):
+        (prototype.makeSimpleSelector):
+        (prototype.makeSelector):
+        (prototype.get randomColorComponent):
+        (prototype.makeDeclaration):
+        (prototype.makeRule):
+        (prototype.makeStylesheet):
+        (prototype.makeStyle):
+        (prototype.makeElement):
+        (prototype.makeTreeWithDepth):
+        (prototype.makeTree):
+        (prototype.updateCachedTestElements):
+        (prototype.randomTreeElement):
+        (prototype.addClasses):
+        (prototype.removeClasses):
+        (prototype.addLeafElements):
+        (prototype.removeLeafElements):
+        (prototype.async.runForever):
+        * StyleBench/resources/tests.js: Added.
+        (makeSteps):
+        (makeSuite):
+
+            Generates Speedometer Suites.
+
 2017-11-29  Robin Morisset  <[email protected]>
 
         The recursive tail call optimisation is wrong on closures

Added: trunk/PerformanceTests/StyleBench/InteractiveRunner.html (0 => 225324)


--- trunk/PerformanceTests/StyleBench/InteractiveRunner.html	                        (rev 0)
+++ trunk/PerformanceTests/StyleBench/InteractiveRunner.html	2017-11-30 12:30:25 UTC (rev 225324)
@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Speedometer 2.0 Interactive Runner</title>
+<script src="" defer></script>
+<script src="" defer></script>
+<script src="" defer></script>
+<style>
+iframe { border: 1px solid black; }
+ol { list-style: none; margin: 0; padding: 0; }
+ol ol { margin-left: 2em; list-position: outside; }
+.running { text-decoration: underline; }
+.ran { color: grey; }
+nav { position: absolute; right: 10px; height: 600px; }
+nav > ol { height: 100%; overflow-y: scroll; }
+</style>
+</head>
+<body>
+<script>
+
+function formatTestName(suiteName, testName) {
+    return suiteName + (testName ? '/' + testName : '');
+}
+
+function createUIForSuites(suites, onstep, onrun) {
+    var control = document.createElement('nav');
+    var ol = document.createElement('ol');
+    var checkboxes = [];
+    for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
+        var suite = suites[suiteIndex];
+        var li = document.createElement('li');
+        var checkbox = document.createElement('input');
+        checkbox.id = suite.name;
+        checkbox.type = 'checkbox';
+        checkbox.checked = !suite.disabled;
+        checkbox._onchange_ = (function (suite, checkbox) { return function () { suite.disabled = !checkbox.checked; } })(suite, checkbox);
+        checkbox.onchange();
+        checkboxes.push(checkbox);
+
+        li.appendChild(checkbox);
+        var label = document.createElement('label');
+        label.appendChild(document.createTextNode(formatTestName(suite.name)));
+        li.appendChild(label);
+        label.htmlFor = checkbox.id;
+
+        var testList = document.createElement('ol');
+        for (var testIndex = 0; testIndex < suite.tests.length; testIndex++) {
+            var testItem = document.createElement('li');
+            var test = suite.tests[testIndex];
+            var anchor = document.createElement('a');
+            anchor.id = suite.name + '-' + test.name;
+            test.anchor = anchor;
+            anchor.appendChild(document.createTextNode(formatTestName(suite.name, test.name)));
+            testItem.appendChild(anchor);
+            testList.appendChild(testItem);
+        }
+        li.appendChild(testList);
+
+        ol.appendChild(li);
+    }
+
+    control.appendChild(ol);
+
+    var button = document.createElement('button');
+    button.textContent = 'Step';
+    button._onclick_ = onstep;
+    control.appendChild(button);
+
+    var button = document.createElement('button');
+    button.textContent = 'Run';
+    button.id = 'runSuites';
+    button._onclick_ = onrun;
+    control.appendChild(button);
+
+    var button = document.createElement('button');
+    button.textContent = 'Select all';
+    button._onclick_ = function () {
+        for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
+            suites[suiteIndex].disabled = false;
+            checkboxes[suiteIndex].checked = true;
+        }
+    };
+    control.appendChild(button);
+
+    var button = document.createElement('button');
+    button.textContent = 'Unselect all';
+    button._onclick_ = function () {
+        for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
+            suites[suiteIndex].disabled = true;
+            checkboxes[suiteIndex].checked = false;
+        }
+
+    };
+    control.appendChild(button);
+
+    return control;
+}
+
+var parseQueryString = (function (pairList) {
+    var pairs = {};
+    for (var i = 0; i < pairList.length; ++i) {
+        var keyValue = pairList[i].split('=', 2);
+        if (keyValue.length == 1)
+            pairs[keyValue[0]] = '';
+        else
+            pairs[keyValue[0]] = decodeURIComponent(keyValue[1].replace(/\+/g, ' '));
+    }
+    return pairs;
+})(window.location.search.substr(1).split('&'));
+
+function disableAllSuitesExcept(suiteName) {
+    Suites.forEach(function(element) {
+        if (element.name !== suiteName)
+            element.disabled = true;
+    });
+}
+
+function startTest() {
+    var queryParam = parseQueryString['suite'];
+    if (queryParam !== undefined)
+        disableAllSuitesExcept(queryParam);
+
+    var runner = new BenchmarkRunner(Suites, {
+        willRunTest: function (suite, test) {
+            test.anchor.classList.add('running');
+        },
+        didRunTest: function (suite, test) {
+            var classList = test.anchor.classList;
+            classList.remove('running');
+            classList.add('ran');
+        },
+        didRunSuites: function (measuredValues) {
+            var results = '';
+            for (var suiteName in measuredValues.tests) {
+                var suiteResults = measuredValues.tests[suiteName];
+                for (var testName in suiteResults.tests) {
+                    var testResults = suiteResults.tests[testName];
+                    for (var subtestName in testResults.tests) {
+                        results += suiteName + ' : ' + testName + ' : ' + subtestName
+                            + ': ' + testResults.tests[subtestName] + ' ms\n';
+                    }
+                }
+                results += suiteName + ' : ' + suiteResults.total + ' ms\n';
+            }
+            results += 'Arithmetic Mean : ' + measuredValues.mean  + ' ms\n';
+            results += 'Geometric Mean : ' + measuredValues.geomean  + ' ms\n';
+            results += 'Total : ' + measuredValues.total + ' ms\n';
+            results += 'Score : ' + measuredValues.score + ' rpm\n';
+
+            if (!results)
+                return;
+
+            var pre = document.createElement('pre');
+            document.body.appendChild(pre);
+            pre.textContent = results;
+        }
+    });
+
+    var currentState = null;
+
+    // Don't call step while step is already executing.
+    document.body.appendChild(createUIForSuites(Suites,
+        function () { runner.step(currentState).then(function (state) { currentState = state; }); },
+        function () { runner.runAllSteps(currentState); currentState = null; }));
+
+    if (parseQueryString['startAutomatically'] !== undefined)
+        document.getElementById('runSuites').click();
+}
+
+window.addEventListener('load', startTest);
+
+</script>
+</body>
+</html>

Added: trunk/PerformanceTests/StyleBench/index.html (0 => 225324)


--- trunk/PerformanceTests/StyleBench/index.html	                        (rev 0)
+++ trunk/PerformanceTests/StyleBench/index.html	2017-11-30 12:30:25 UTC (rev 225324)
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>StyleBench 0.1</title>
+    <link rel="stylesheet" href=""
+    <script src="" defer></script>
+    <script src="" defer></script>
+    <script src="" defer></script>
+    <script src="" defer></script>
+    <script src="" defer></script>
+    <script src="" defer></script>
+    <script>
+        addEventListener('load', () => {
+            if (!window.location.protocol.startsWith('http'))
+                showSection('local-message', false);
+        });
+    </script>
+</head>
+<body>
+<main>
+    <a id="logo-link" href=""
+
+    <section id="home" class="selected">
+        <p>
+            StyleBench is a browser benchmark that measures the performance of the style resolution mechanism.
+        </p>
+        <p id="screen-size-warning"><strong>
+            Your browser window is too small. For most accurate results, please make the view port size at least 850px by 650px.<br>
+            It's currently <span id="screen-size"></span>.
+        </strong>
+        <div class="buttons">
+            <button _onclick_="startTest()">Start Test</button>
+        </div>
+        <p class="show-about"><a href="" StyleBench</a></p>
+    </section>
+
+    <section id="running">
+        <div id="testContainer"></div>
+        <div id="progress"><div id="progress-completed"></div></div>
+        <div id="info"></div>
+    </section>
+
+    <section id="summarized-results">
+        <h1>Runs / Minute</h1>
+        <div class="gauge"><div class="window"><div class="needle"></div></div></div>
+        <hr>
+        <div id="result-number"></div>
+        <div id="confidence-number"></div>
+        <div class="buttons">
+            <button _onclick_="startTest()">Test Again</button>
+            <button class="show-details" _onclick_="showResultDetails()">Details</button>
+        </div>
+    </section>
+
+    <section id="detailed-results">
+        <h1>Detailed Results</h1>
+        <table class="results-table"></table>
+        <table class="results-table"></table>
+        <div class="arithmetic-mean"><label>Arithmetic Mean:</label><span id="results-with-statistics"></span></div>
+        <div class="buttons">
+            <button _onclick_="startTest()">Test Again</button>
+            <button id="show-summary" _onclick_="showResultsSummary()">Summary</button>
+        </div>
+        <p class="show-about"><a href="" StyleBench</a></p>
+    </section>
+
+    <section id="about">
+        <h1>About StyleBench</h1>
+
+        <p>StyleBench tests performance of CSS style resolution and style invalidation. Each test run creates a large document and a large stylesheet using varying settings. It then applies a series of mutations to the document and measures the time to update the rendering. The resulting layout is simple, most of the pressure is on selector matching.</p>
+
+        <p>StyleBench uses Speedometer framework for UI and measurements.</p>
+    </section>
+    <section id="local-message">
+        <h2>Access via 'file:' protocol</h1>
+        <p>To run locally, launch a web server under PerformanceTests directory with 'python -m SimpleHTTPServer 8001' and access via <a href=""
+        </p>
+        <p>
+            Individual tests (without measurement) can also be run locally by opening <a href=""
+        </p>
+    </section>
+</main>
+</body>
+</html>

Added: trunk/PerformanceTests/StyleBench/resources/style-bench.html (0 => 225324)


--- trunk/PerformanceTests/StyleBench/resources/style-bench.html	                        (rev 0)
+++ trunk/PerformanceTests/StyleBench/resources/style-bench.html	2017-11-30 12:30:25 UTC (rev 225324)
@@ -0,0 +1,32 @@
+<!doctype html>
+<script src=""
+<body>
+<div id="testroot"></div>
+<div id="controls">
+<select></select>
+<button _onclick_="createBenchmarkFromSelect()">Initialize</button>
+<button _onclick_="createBenchmarkFromSelect().runForever()">Initialize and run</button>
+</div>
+<script>
+const configurations = StyleBench.predefinedConfigurations();
+
+const select = document.querySelector("#controls select");
+for (const configuration of configurations) {
+    const option = document.createElement("option");
+    option.innerHTML = configuration.name;
+    select.appendChild(option);
+}
+
+function createBenchmark(configuration)
+{
+    controls.remove();
+
+    return new StyleBench(configuration);
+}
+
+function createBenchmarkFromSelect()
+{
+    return createBenchmark(configurations[select.selectedIndex]);
+}
+</script>
+

Added: trunk/PerformanceTests/StyleBench/resources/style-bench.js (0 => 225324)


--- trunk/PerformanceTests/StyleBench/resources/style-bench.js	                        (rev 0)
+++ trunk/PerformanceTests/StyleBench/resources/style-bench.js	2017-11-30 12:30:25 UTC (rev 225324)
@@ -0,0 +1,385 @@
+class Random
+{
+    constructor(seed)
+    {
+        this.seed = seed % 2147483647;
+        if (this.seed <= 0)
+            this.seed += 2147483646;
+    }
+
+    get next()
+    {
+        return this.seed = this.seed * 16807 % 2147483647;
+    }
+
+    chance(chance)
+    {
+        return this.next % 1048576 < chance * 1048576;
+    }
+
+    number(under)
+    {
+        return this.next % under;
+    }
+}
+
+function nextAnimationFrame()
+{
+    return new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+class StyleBench
+{
+    static defaultConfiguration()
+    {
+        return {
+            name: 'Default',
+            elementTypeCount: 10,
+            elementChance: 0.5,
+            classCount: 200,
+            classChance: 0.3,
+            combinators: [' ', '>',],
+            pseudoClasses: [],
+            pseudoClassChance: 0,
+            beforeAfterChance: 0,
+            maximumSelectorLength: 6,
+            ruleCount: 5000,
+            elementCount: 20000,
+            maximumTreeDepth: 6,
+            maximumTreeWidth: 50,
+            repeatingSequenceChance: 0.2,
+            repeatingSequenceMaximumLength: 3,
+            leafClassMutationChance: 0.1,
+            styleSeed: 1,
+            domSeed: 2,
+        };
+    }
+
+    static descendantCombinatorConfiguration()
+    {
+        return Object.assign(this.defaultConfiguration(), {
+            name: 'Descendant and child combinators',
+        });
+    }
+
+    static siblingCombinatorConfiguration()
+    {
+        return Object.assign(this.defaultConfiguration(), {
+            name: 'Sibling combinators',
+            combinators: [' ', ' ', '>', '>', '~', '+',],
+        });
+    }
+
+    static pseudoClassConfiguration()
+    {
+        return Object.assign(this.defaultConfiguration(), {
+            name: 'Positional pseudo classes',
+            pseudoClassChance: 0.1,
+            pseudoClasses: [
+                'nth-child(2n+1)',
+                'nth-last-child(3n)',
+                'nth-of-type(3n)',
+                'nth-last-of-type(4n)',
+                'first-child',
+                'last-child',
+                'first-of-type',
+                'last-of-type',
+                'only-of-type',
+            ],
+        });
+    }
+
+    static beforeAndAfterConfiguration()
+    {
+        return Object.assign(this.defaultConfiguration(), {
+            name: 'Before and after pseudo elements',
+            beforeAfterChance: 0.1,
+        });
+    }
+
+    static predefinedConfigurations()
+    {
+        return [
+            this.descendantCombinatorConfiguration(),
+            this.siblingCombinatorConfiguration(),
+            this.pseudoClassConfiguration(),
+            this.beforeAndAfterConfiguration(),
+        ];
+    }
+
+    constructor(configuration)
+    {
+        this.configuration = configuration;
+
+        this.baseStyle = document.createElement("style");
+        this.baseStyle.textContent = `
+            #testroot {
+                font-size: 10px;
+                line-height: 10px;
+            }
+            #testroot * {
+                display: inline-block;
+            }
+            #testroot :empty {
+                width:10px;
+                height:10px;
+            }
+        `;
+        document.head.appendChild(this.baseStyle);
+
+        this.random = new Random(this.configuration.styleSeed);
+        this.makeStyle();
+
+        this.random = new Random(this.configuration.domSeed);
+        this.makeTree();
+    }
+
+    randomElementName()
+    {
+        const elementTypeCount = this.configuration.elementTypeCount;
+        return `elem${ this.random.number(elementTypeCount) }`;
+    }
+
+    randomClassName()
+    {
+        const classCount = this.configuration.classCount;
+        return `class${ this.random.number(classCount) }`;
+    }
+
+    randomClassNameFromRange(range)
+    {
+        const maximum = Math.round(range * this.configuration.classCount);
+        return `class${ this.random.number(maximum) }`;
+    }
+
+    randomCombinator()
+    {
+        const combinators = this.configuration.combinators;
+        return combinators[this.random.number(combinators.length)]
+    }
+
+    randomPseudoClass()
+    {
+        const pseudoClasses = this.configuration.pseudoClasses;
+        return pseudoClasses[this.random.number(pseudoClasses.length)]
+    }
+
+    makeSimpleSelector(index, length)
+    {
+        const isLast = index == length - 1;
+        const usePseudoClass = this.random.chance(this.configuration.pseudoClassChance) && this.configuration.pseudoClasses.length;
+        const useElement = usePseudoClass || this.random.chance(this.configuration.elementChance); // :nth-of-type etc only make sense with element
+        const useClass = !useElement || this.random.chance(this.configuration.classChance);
+        const useBeforeOrAfter = isLast && this.random.chance(this.configuration.beforeAfterChance);
+        let result = "";
+        if (useElement)
+            result += this.randomElementName();
+        if (useClass) {
+            // Use a smaller pool of class names on the left side of the selectors to create containers.
+            result += "." + this.randomClassNameFromRange((index + 1) / length);
+        }
+        if (usePseudoClass)
+            result +=  ":" + this.randomPseudoClass();
+        if (useBeforeOrAfter) {
+            if (this.random.chance(0.5))
+                result +=  "::before";
+            else
+                result +=  "::after";
+        }
+        return result;
+    }
+
+    makeSelector()
+    {
+        const length = this.random.number(this.configuration.maximumSelectorLength) + 1;
+        let result = this.makeSimpleSelector(0, length);
+        for (let i = 0; i < length; ++i) {
+            const combinator = this.randomCombinator();
+            if (combinator != ' ')
+                result += " " + combinator;
+            result += " " + this.makeSimpleSelector(i, length);
+        }
+        return result;
+    }
+
+    get randomColorComponent()
+    {
+        return this.random.next % 256;
+    }
+
+    makeDeclaration(selector)
+    {
+        let declaration = `background-color: rgb(${this.randomColorComponent}, ${this.randomColorComponent}, ${this.randomColorComponent});`;
+
+        if (selector.endsWith('::before') || selector.endsWith('::after'))
+            declaration += " content: '\xa0';";
+
+        return declaration;
+    }
+
+    makeRule()
+    {
+        const selector = this.makeSelector();
+        return selector + " { " + this.makeDeclaration(selector) + " }";
+    }
+
+    makeStylesheet(size)
+    {
+        let cssText = "";
+        for (let i = 0; i < size; ++i)
+            cssText += this.makeRule() + "\n";
+        return cssText;
+    }
+
+    makeStyle()
+    {
+        this.testStyle = document.createElement("style");
+        this.testStyle.textContent = this.makeStylesheet(this.configuration.ruleCount);
+
+        document.head.appendChild(this.testStyle);
+    }
+
+    makeElement()
+    {
+        const element = document.createElement(this.randomElementName());
+        const hasClasses = this.random.chance(0.5);
+        if (hasClasses) {
+            const count = this.random.number(3) + 1;
+            for (let i = 0; i < count; ++i)
+                element.classList.add(this.randomClassName());
+        }
+        return element;
+    }
+
+    makeTreeWithDepth(parent, remainingCount, depth)
+    {
+        const maximumDepth = this.configuration.maximumTreeDepth;
+        const maximumWidth =  this.configuration.maximumTreeWidth;
+        const nonEmptyChance = (maximumDepth - depth) / maximumDepth;
+
+        const shouldRepeat = this.random.chance(this.configuration.repeatingSequenceChance);
+        const repeatingSequenceLength = shouldRepeat ? this.random.number(this.configuration.repeatingSequenceMaximumLength) + 1 : 0;
+
+        let childCount = 0;
+        if (depth == 0)
+            childCount = remainingCount;
+        else if (this.random.chance(nonEmptyChance))
+            childCount = this.random.number(maximumWidth * depth / maximumDepth);
+
+        let repeatingSequence = [];
+        let repeatingSequenceSize = 0;
+        for (let i = 0; i < childCount; ++i) {
+            if (shouldRepeat && repeatingSequence.length == repeatingSequenceLength && repeatingSequenceSize < remainingCount) {
+                for (const subtree of repeatingSequence)
+                    parent.appendChild(subtree.cloneNode(true));
+                remainingCount -= repeatingSequenceSize;
+                if (!remainingCount)
+                    return 0;
+                continue;
+            }
+            const element = this.makeElement();
+            parent.appendChild(element);
+
+            if (!--remainingCount)
+                return 0;
+            remainingCount = this.makeTreeWithDepth(element, remainingCount, depth + 1);
+            if (!remainingCount)
+                return 0;
+
+            if (shouldRepeat && repeatingSequence.length < repeatingSequenceLength) {
+                repeatingSequence.push(element);
+                repeatingSequenceSize += element.querySelectorAll("*").length + 1;
+            }
+        }
+        return remainingCount;
+    }
+
+    makeTree()
+    {
+        this.testRoot = document.querySelector("#testroot");
+        const elementCount = this.configuration.elementCount;
+
+        this.makeTreeWithDepth(this.testRoot, elementCount, 0);
+
+        this.updateCachedTestElements();
+    }
+
+    updateCachedTestElements()
+    {
+        this.testElements = this.testRoot.querySelectorAll("*");
+    }
+
+    randomTreeElement()
+    {
+        const randomIndex = this.random.number(this.testElements.length);
+        return this.testElements[randomIndex]
+    }
+
+    addClasses(count)
+    {
+        for (let i = 0; i < count;) {
+            const element = this.randomTreeElement();
+            // There are more leaves than branches. Avoid skewing towards leaf mutations.
+            if (!element.firstChild && !this.random.chance(this.configuration.leafClassMutationChance))
+                continue;
+            ++i;
+            const classList = element.classList;
+            classList.add(this.randomClassName());
+        }
+    }
+
+    removeClasses(count)
+    {
+        for (let i = 0; i < count;) {
+            const element = this.randomTreeElement();
+            const classList = element.classList;
+            if (!element.firstChild && !this.random.chance(this.configuration.leafClassMutationChance))
+                continue;
+            if (!classList.length)
+                continue;
+            ++i;
+            classList.remove(classList[0]);
+        }
+    }
+
+    addLeafElements(count)
+    {
+        for (let i = 0; i < count;) {
+            const parent = this.randomTreeElement();
+            // Avoid altering tree shape by turning many leaves into containers.
+            if (!parent.firstChild)
+                continue;
+            ++i;
+            const children = parent.childNodes;
+            const index = this.random.number(children.length + 1);
+            parent.insertBefore(this.makeElement(), children[index]);
+        }
+        this.updateCachedTestElements();
+    }
+
+    removeLeafElements(count)
+    {
+        for (let i = 0; i < count;) {
+            const element = this.randomTreeElement();
+
+            const canRemove = !element.firstChild && element.parentNode;
+            if (!canRemove)
+                continue;
+            ++i;
+            element.parentNode.removeChild(element);
+        }
+        this.updateCachedTestElements();
+    }
+
+    async runForever()
+    {
+        while (true) {
+            this.addClasses(10);
+            this.removeClasses(10);
+            this.addLeafElements(10);
+            this.removeLeafElements(10);
+
+            await nextAnimationFrame();
+        }
+    }
+}

Added: trunk/PerformanceTests/StyleBench/resources/tests.js (0 => 225324)


--- trunk/PerformanceTests/StyleBench/resources/tests.js	                        (rev 0)
+++ trunk/PerformanceTests/StyleBench/resources/tests.js	2017-11-30 12:30:25 UTC (rev 225324)
@@ -0,0 +1,37 @@
+function makeSteps(count)
+{
+    let steps = [];
+    for (let i = 0; i < count; ++i) {
+        steps.push(new BenchmarkTestStep('Adding classes', (bench, contentWindow, contentDocument) => {
+            bench.addClasses(100);
+        }));
+        steps.push(new BenchmarkTestStep('Removing classes', (bench, contentWindow, contentDocument) => {
+            bench.removeClasses(100);
+        }));
+        steps.push(new BenchmarkTestStep('Adding leaf elements', (bench, contentWindow, contentDocument) => {
+            bench.addLeafElements(100);
+        }));
+        steps.push(new BenchmarkTestStep('Removing leaf elements', (bench, contentWindow, contentDocument) => {
+            bench.removeLeafElements(100);
+        }));
+    }
+    return steps;
+}
+
+function makeSuite(configuration)
+{
+    return {
+        name: configuration.name,
+        url: 'style-bench.html',
+        prepare: (runner, contentWindow, contentDocument) => {
+            return runner.waitForElement('#testroot').then((element) => {
+                return contentWindow.createBenchmark(configuration);
+            });
+        },
+        tests: makeSteps(5),
+    };
+}
+
+var Suites = [];
+for (const configuration of StyleBench.predefinedConfigurations())
+    Suites.push(makeSuite(configuration));
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to