|

Codegolfing a.k.a. Performance-Wettrennen

Codegolfing ist ein Wettbewerb wo es darum geht eine Lösung in möglichst wenig Code zu implementieren.

Dadurch werden oft auch weniger Befehle benötigt und besonders die Performance optimiert.

Solche Wettrennen kann man recht einfach mit den Lösungen benchmark.js, nanobench, matcha und speedracer durchführen und die Performance der verschiedenen Lösungen vergleichen.

Es gibt auch entsprechende Wettbewerbe zum Beispiel bei StackExchange und es wird dort sehr genau berechnet, wie komplex die Implementierungen sind.

In diesem Beitrag werden wir Funktionen mit benchmark.js gegeneinander antreten lassen, um zum Beispiel anhand der Resultate eine technische Entscheidung für die Implementierung zu treffen.

Dafür installieren wir die Abhängigkeiten benchmarkund microtime für genauere Ergebnisse.

npm i --save benchmark microtime

Anschließend erstellen wir ein paar einfache Tests verschiedener Implementierungen, die jeweils das gleiche machen aber unterschiedlich schnell sind.

Dazu legen wir eine index.js an und fügen folgenden Code ein.

if (
    typeof window === 'undefined'
) {
    var Benchmark = require('benchmark')
}

const suite = new Benchmark.Suite

suite.add('parseInt', function () {
        parseInt('123')
    })
    .add('binary', function () {
        +'123'
    })
    .on('cycle', function (event) {
        console.log(String(event.target))
    })
    .on('complete', function () {
        console.log('Fastest is ' + this.filter('fastest').map('name'))
    })
    .run({
        'async': false
    })

/**
 * browser based tests
 */
if (
    typeof window !== 'undefined' &&
    window.browser
) {
    var browserSuite = new Benchmark.Suite;

    browserSuite.add('for', function () {
            var links = document.querySelectorAll('a')
            var linksLength = links.length
            var href
            for (var i = 0; i < linksLength; i++) {
                href = links[i].href
            }
        })
        .add('forEach', function () {
            var href
            document.querySelectorAll('a').forEach(function (el) {
                href = el.href
            })
        })
        .on('cycle', function (event) {
            console.log(String(event.target))
        })
        .on('complete', function () {
            console.log('Fastest is ' + this.filter('fastest').map('name'))
        })
        .run({
            'async': false
        })
}

{2} Wir prüfen erstmal ob wir in einer Umgebung mit window-Objekt sind. Ist dies nicht der Fall sind wir in der Regel auch nicht in einem Browser.

{4} Wenn dies der Fall ist dann laden wir benchmark.js und machen es per Benchmark verfügbar.

{7} Für die weiteren Tests erstellen wir mit Benchmark.Suite eine neue Testsuite.

{9} Zu der Testsuite fügen wir nun per add() mehrere Tests hinzu. Dazu definieren wir erstmal in jeweils einen Namen für den jeweiligen Test und definieren das Verhalten in einer anonymen Funktion.

{15} Nach jedem fertigen Durchlauf eines Tests geben wir die Benchmark-Resultate aus. Dies geschieht über das cycle-Event.

{18} Wenn alle Tests dieser Testsuite durchgelaufen sind geben wir aus, welcher Test am schnellsten war.

{21} Damit auch was passiert müssen per run() die Tests gestartet werden.

{22} Da wir die Tests einzeln nacheinander ausführen wollen müssen wir die asynchrone Ausführung der Tests per Konfiguration deaktivieren.

{30} Bei den Browser-basierten Tests prüfen wir noch eine window-Variable die nur in index.html gesetzt wird und in Browser-Umgebungen somit verfügbar ist.

{35} In den Browser-Tests können wir nun auf die üblichen DOM-Methoden zugreifen.

Der Rest der Datei ist wie bei den bisherigen Tests gleich, nur haben wir hier 2 Browser-basierte Tests die auf den DOM zugreifen und in einer echten Browser-Umgebung getestet werden.

Anschließend erstellen wir noch die benötigten Dateien für die Browser-basierten Tests.
Dazu müssen wir per Node.js ein paar Dateien aus node_modules kopieren.
Dies geht zum Beispiel einfach per fs-extra.

npm i --save fs-extra

In einer copy-assets.js Datei fügen wir dann die Befehle ein, die zum Kopieren der benötigten Dateien per node copy-assets.js ausgeführt werden.

const fs = require('fs-extra')

fs.copy('node_modules/lodash/lodash.min.js', 'lodash.min.js')
fs.copy('node_modules/benchmark/benchmark.js', 'benchmark.js')
fs.copy('node_modules/platform/platform.js', 'platform.js')

Damit wir auch im Browser testen können und darüber auch DOM-Zugriffe in unserem Benchmark durchführen können, legen wir eine einfache index.html an.

Diese lädt auch die benötigten Abhängigkeiten und unsere Tests in index.js.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <a href="1"></a>
    <a href="2"></a>
    <a href="3"></a>
    <a href="4"></a>
    <a href="5"></a>
    <a href="6"></a>
    <script>
        window.browser = true
    </script>
    <script src="lodash.min.js"></script>
    <script src="platform.js"></script>
    <script src="benchmark.js"></script>
    <script src="index.js"></script>
</body>
</html>

Wenn wir nun per node index.js die Benchmarks anstoßen sehen wir nach einiger Zeit die Ergebnisse und als Resultat wird der schnellste Test pro Testsuite ausgegeben.

$ node index.js
parseInt x 96,043,794 ops/sec ±0.33% (93 runs sampled)
binary x 788,249,466 ops/sec ±0.26% (95 runs sampled)
Fastest is binary

Wenn wir nun index.html im Browser aufrufen sehen wir in der Browserkonsole ebenfalls die Resultate der Benchmarks, dieses Mal aber auch die Benchmarks für Browser.

parseInt x 94,072,484 ops/sec ±0.96% (62 runs sampled)
index.js:17 binary x 783,457,297 ops/sec ±0.34% (63 runs sampled)
index.js:20 Fastest is binary
index.js:50 for x 165,760 ops/sec ±2.94% (61 runs sampled)
index.js:50 forEach x 122,776 ops/sec ±2.49% (60 runs sampled)
index.js:53 Fastest is for

Den Code dazu findet man hier.

Weitere interessante Beiträge im Bereich Performance gibt es zum Beispiel im Performance Calendar.