|
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 benchmark
und 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.