关于任务的一点点:
我有一个函数(js),它接受一个参数并返回一个由数学公式计算的值
var f = function(x) {
return 3*Math.sin(2*x)*Math.pow(Math.cos(x), 4);
}
我还有一个数组,索引是区间数(从零开始索引),索引元素的值是该值落入该索引的次数。
比如我数组中有2个元素,我感兴趣的值是从0到1,那么如果f(x)返回一个小于0.5的数,那么数组的第一个元素会增加加 1,如果大于 0.5,则第二个元素将相应增加 1。
我修改了函数,使其立即返回给定参数的值将落入的区间数。
var hist_min = 0;
var hist_max = 2;
var hist_range = hist_max-hist_min;
var hist_cols = 30;
var f = function(x) {
return Math.floor(((3*Math.sin(2*x)*Math.pow(Math.cos(x), 4)-hist_min)/hist_range) * hist_cols);
}
在上面的例子中,我有 30 个区间,最左边的左边框是 0,最右边的右边是 2。
接下来,我测量了这个函数 3 * 10^6 次迭代的速度。
var iteratitions = 3000000;
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
var need_col = f(Math.random())
}
console.log("Finished in",Date.now()-start_time+"ms")
结果很快,在我的电脑上 ~ 100-200ms
但是,我需要以某种方式存储这些间隔,例如,在一个数组中。
我这样做
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
var need_col = f(Math.random())
arr[need_col]++;
}
console.log("Finished in",Date.now()-start_time+"ms")
然而,这已经花费了更长的时间~1300ms
起初我以为这是因为arr[need_col]++
它需要很长时间才能完成,但如果你这样做:
for (var i=0; i < iteratitions; i++) {
var need_col = f(Math.random())
var need_col2 = 3
arr[need_col2]++;
}
这已经在 ~130ms 内完成
问题出现了,为什么如果我只是将函数 f 的值存储在 need_col 变量中,一切都会很快运行,但是一旦我使用 need_col 变量(将其作为索引传递),那么有时一切都会变慢?
我把所有的代码
var hist_min = 0;
var hist_max = 2;
var hist_range = hist_max-hist_min;
var hist_cols = 30;
var need_col = 0;
/*
var f = function(x) {
return 3*Math.sin(2*x)*Math.pow(Math.cos(x), 4)
}
*/
var f = function(x) {
return Math.floor(((3*Math.sin(2*x)*Math.pow(Math.cos(x), 4)-hist_min)/hist_range) * hist_cols);
}
var iteratitions = 3000000;
var arr = [];
// инициализация
for (var i=0; i < hist_cols; i++) {
arr.push(0)
}
// пустой цикл занимает время
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
}
console.log("empty loop finished in",Date.now()-start_time+"ms")
// просто увеличение элемента массива по индексу
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
var need_col = 3
arr[need_col]++;
}
console.log("arr[need_col]++; Finished in",Date.now()-start_time+"ms")
// просто выполение f(x)
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
need_col = f(Math.random())
//var need_col2 = 3
//arr[need_col2]++;
}
console.log("just f(x) finished in",Date.now()-start_time+"ms")
//console.log(need_col)
// заполнение массива тем, что нам нужно
var start_time = Date.now();
for (var i=0; i < iteratitions; i++) {
var need_col = f(Math.random())
arr[need_col]++;
}
console.log("filling array f(x) finished in",Date.now()-start_time+"ms")
console.log("arr=", arr)
在这个特定的问题中,问题在于
3*Math.sin(2*x)*Math.pow(Math.cos(x), 4)
区间上的函数[0, 1)
具有从 about0
到 about的值1.553
,但是对区间数的转换函数(在问题更改之前)是针对值 from0
到计算的1
。因此,对于大于1
调用的值arr[need_col]++
超出了数组的边界,这导致了强烈的减速(以及NaN
数组中的值)。比较
for(..) { f(x); }
不for(..) { arr[f(x)]++; }
正确。在第一种情况下,引擎知道函数调用的结果没有被使用,并节省了返回值。如果他注意到这通常是一个纯函数,那么理论上他可能根本不会调用它。与 (for example) 比较更正确
s = 0; for(..) { s += f(x) }
,这里的结果与访问数组没有太大区别。因此,我们可以假设在 JS 框架内可以完成的所有事情都已经完成。如果您需要更快,那么您需要查看其他 PL,但这超出了此问题的范围。
索引是一个字符串,时间差在数组getter里面的need_col.toString()。