|
(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
yieldis such a performance drag in JS; naturally, this can only be done with fair tests, not with flawed ones.Caveats and Discussion
forloop with numerical index; when I sayfor/fromloops that's a JSfor/ofloop. Sorry for the confusion.probeslist for each test run; however, the time needed for these setups is not included in the timings.array.values()when you can iterate overarrayitself. 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.probelists is not included in the timings; this is why each test case is a set of two nested async functions.probelists 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 classicalforloops). The~10 : 1 ... ~20 : 1speed advantage offorloops was enough to allow my old, convoluted formulation to run circles around a very clean and simple, three-layers deep cascade of nestedyield from generatorstatements.Verdicts
yieldhas a downright incredible/horrible effect on JS performance.yieldis such a great tool it should be fast, too.yieldthat 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.