Как это ускорить (почему это так медленно работает)?

Немного о задаче:
У меня есть функция (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)

P.S. на моём компьютере немного другие цифры (я выполняю код через node) вывод консоли


Ответы (2 шт):

Автор решения: lijil

Индекс это строка, разница во времени посути в need_col.toString() внутри гетера массива.

new Proxy ([ 1 ],{
    get : function ( target , key ){
        console . log ( target , typeof key , key );
        // [ 1 ] string 0
    }
})[ 0 ] ++ ;
→ Ссылка
Автор решения: Alexey Ten

В данном конкретном вопросе проблема в том, что функция 3*Math.sin(2*x)*Math.pow(Math.cos(x), 4) на отрезке [0, 1) имеет значения о 0 до примерно 1.553, но функция пересчёта в номер интервала (до изменения вопроса) была рассчитана на значения от 0 до 1. Поэтому для значений больше 1 вызов arr[need_col]++ выходил за границы массива и это приводило к сильному замедлению (и значениям NaN в массиве).

Сравнение for(..) { f(x); } и for(..) { arr[f(x)]++; } некорректно. В первом случае движок понимает, что результат вызова функции не используется и экономит на возврате значения. А если заметит, что это вообще чистая функция, то в теории может её вообще не вызывать.

Корректнее сравнивать с (например) s = 0; for(..) { s += f(x) }, и тут результат не сильно отличается от доступа к массиву.

Таким образом можно считать, что всё что можно было сделать в рамках JS уже сделано. Если нужно быстрее, то надо смотреть в сторону других ЯП, но это выходит за рамки данного вопроса.

→ Ссылка