|
(function() { |
|
'use strict'; |
|
var fn, log, warn; |
|
|
|
//########################################################################################################### |
|
log = console.log; |
|
|
|
warn = console.warn; |
|
|
|
fn = (new Intl.NumberFormat('en-US')).format; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this._get_addup_probe = function(n) { |
|
var base, i, len, matcher, number, probe, ref; |
|
base = 1e6; |
|
probe = (function() { |
|
var results = []; |
|
for (var i = base, ref = base + n; base <= ref ? i < ref : i > ref; base <= ref ? i++ : i--){ results.push(i); } |
|
return results; |
|
}).apply(this); |
|
matcher = 0; |
|
for (i = 0, len = probe.length; i < len; i++) { |
|
number = probe[i]; |
|
matcher += number; |
|
} |
|
return {probe, matcher, base}; |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_for_in_loop = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_for_in_loop; |
|
return new Promise(addup_for_in_loop = (resolve) => { |
|
var count, i, len, number, sum; |
|
count = 0; |
|
sum = 0; |
|
for (i = 0, len = probe.length; i < len; i++) { |
|
number = probe[i]; |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_for_from_loop = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_for_from_loop; |
|
return new Promise(addup_for_from_loop = (resolve) => { |
|
var count, number, sum; |
|
count = 0; |
|
sum = 0; |
|
for (number of probe) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_forEach_loop = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_forEach_loop; |
|
return new Promise(addup_forEach_loop = (resolve) => { |
|
var count, sum; |
|
count = 0; |
|
sum = 0; |
|
probe.forEach(function(number) { |
|
count++; |
|
return sum += number; |
|
}); |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_yield_for_in_array = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, numbers, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
numbers = probe; |
|
probe = function*() { |
|
var i, len, results; |
|
results = []; |
|
for (i = 0, len = numbers.length; i < len; i++) { |
|
n = numbers[i]; |
|
results.push((yield n)); |
|
} |
|
return results; |
|
}; |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_yield_for_in_array; |
|
return new Promise(addup_yield_for_in_array = (resolve) => { |
|
var count, number, ref, sum; |
|
count = 0; |
|
sum = 0; |
|
ref = probe(); |
|
for (number of ref) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_yield_for_from_values = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, numbers, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
numbers = probe; |
|
probe = function*() { |
|
var ref, results; |
|
ref = numbers.values(); |
|
results = []; |
|
for (n of ref) { |
|
results.push((yield n)); |
|
} |
|
return results; |
|
}; |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_yield_for_from_values; |
|
return new Promise(addup_yield_for_from_values = (resolve) => { |
|
var count, number, ref, sum; |
|
count = 0; |
|
sum = 0; |
|
ref = probe(); |
|
for (number of ref) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_yield_from_array = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, numbers, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
numbers = probe; |
|
probe = function*() { |
|
return (yield* numbers); |
|
}; |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_yield_from_array; |
|
return new Promise(addup_yield_from_array = (resolve) => { |
|
var count, number, ref, sum; |
|
count = 0; |
|
sum = 0; |
|
ref = probe(); |
|
for (number of ref) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_yield_from_values = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, numbers, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
numbers = probe; |
|
probe = function*() { |
|
return (yield* numbers.values()); |
|
}; |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_yield_from_values; |
|
return new Promise(addup_yield_from_values = (resolve) => { |
|
var count, number, ref, sum; |
|
count = 0; |
|
sum = 0; |
|
ref = probe(); |
|
for (number of ref) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.addup_values_iterator = function(n) { |
|
return new Promise((resolve) => { |
|
var matcher, numbers, probe; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
numbers = probe; |
|
probe = numbers.values(); |
|
//......................................................................................................... |
|
resolve(() => { |
|
var addup_values_iterator; |
|
return new Promise(addup_values_iterator = (resolve) => { |
|
var count, number, sum; |
|
({probe, matcher} = this._get_addup_probe(n)); |
|
count = 0; |
|
sum = 0; |
|
for (number of probe) { |
|
count++; |
|
sum += number; |
|
} |
|
if (sum !== matcher) { |
|
warn(`incorrect result, expected ${matcher}, got ${sum}`); |
|
} |
|
resolve(count); |
|
return null; |
|
}); |
|
}); |
|
//......................................................................................................... |
|
return null; |
|
}); |
|
}; |
|
|
|
//----------------------------------------------------------------------------------------------------------- |
|
this.benchmark = async function() { |
|
var baseline_dt, delta_ts, dt, dt_txt, frequency, frequency_txt, i, j, len, len1, n, percentage, percentage_txt, t0, t1, test, test_name, test_name_txt, test_names; |
|
// n = 5e6 |
|
n = 5e6; |
|
test_names = ['addup_for_in_loop', 'addup_for_from_loop', 'addup_values_iterator', 'addup_forEach_loop', 'addup_yield_from_values', 'addup_yield_for_in_array', 'addup_yield_from_array', 'addup_yield_for_from_values']; |
|
delta_ts = []; |
|
//......................................................................................................... |
|
for (i = 0, len = test_names.length; i < len; i++) { |
|
test_name = test_names[i]; |
|
log(`test: ${test_name}`); |
|
test = (await this[test_name](n)); |
|
t0 = Date.now(); |
|
await test(); |
|
t1 = Date.now(); |
|
delta_ts.push({ |
|
test_name, |
|
dt: t1 - t0 |
|
}); |
|
} |
|
//......................................................................................................... |
|
delta_ts.sort(function(a, b) { |
|
if (a.dt < b.dt) { |
|
return -1; |
|
} |
|
if (a.dt > b.dt) { |
|
return +1; |
|
} |
|
return 0; |
|
}); |
|
//......................................................................................................... |
|
baseline_dt = delta_ts[0].dt; |
|
for (j = 0, len1 = delta_ts.length; j < len1; j++) { |
|
({test_name, dt} = delta_ts[j]); |
|
test_name_txt = `${test_name} `.padEnd(30, '.'); |
|
dt_txt = ` ${fn(dt)} ms`.padStart(10, ' '); |
|
frequency = n / (dt / 1000); |
|
frequency_txt = ` ${fn(frequency.toFixed(0))} Hz`.padStart(25, ' '); |
|
percentage = baseline_dt / dt * 100; |
|
percentage_txt = ` ${percentage.toFixed(1)} %`.padStart(8, ' '); |
|
log(test_name_txt + dt_txt + frequency_txt + percentage_txt); |
|
} |
|
//......................................................................................................... |
|
return null; |
|
}; |
|
|
|
(async() => { //########################################################################################################### |
|
await this.benchmark(); |
|
return log('ok'); |
|
})(); |
|
|
|
}).call(this); |
If you spot any flaws in the code above, please comment. The whole point of this exercise is to find out why
yield
is such a performance drag in JS; naturally, this can only be done with fair tests, not with flawed ones.Caveats and Discussion
for
loop with numerical index; when I sayfor/from
loops that's a JSfor/of
loop. Sorry for the confusion.probes
list for each test run; however, the time needed for these setups is not included in the timings.array.values()
when you can iterate overarray
itself. Rather, the intent was to provide enough similar scenarios so as to enable a differential analysis of which factors in a given looping construct could have caused an improvement or a deterioration.probe
lists is not included in the timings; this is why each test case is a set of two nested async functions.probe
lists such as 1.000 and 100.000 numbers; then, results are more volatile, and, more interestingly, the differences between wieners and lusers become less pronounced.yield
's effect on data throughput in a rather more complicated setting (testing a conceptually MUCH simpler,yield
-based re-implementation of SteamPipes, which internally uses classicalfor
loops). The~10 : 1 ... ~20 : 1
speed advantage offor
loops was enough to allow my old, convoluted formulation to run circles around a very clean and simple, three-layers deep cascade of nestedyield from generator
statements.Verdicts
yield
has a downright incredible/horrible effect on JS performance.yield
is such a great tool it should be fast, too.yield
that has problems, the performance ofArray::forEach()
isn't great, either, and even replacing (JS)for (i = 0, len = probe.length; i < len; i++) { ... }
with (JS)for (number of probe) { ... }
basically destroys loop throughput, and this is another thing I find hard to believe.