使用Math.pow()和Math.floor()调用在循环内简化函数

时间:2018-06-03 20:40:32

标签: javascript performance loops pow floor

如何简化以下功能以及重复调用Math.pow()

function x(a, b, c) {
    rv = Math.floor(a * Math.pow(2, b));

    for (i = 1; i < c; i++) {
        rv += Math.floor(a * Math.pow(1.2, b + i));
    }

    return rv;
}

2 个答案:

答案 0 :(得分:1)

根据您问题中的标记,我假设您希望改善该代码的效果。

以下是一些您可以使用的技术,或多或少按照它们为您的代码带来的性能提升排序,但第一个除外,但我鼓励您逐一在代码上实现它们,看看如何它们会单独影响其性能。

✏️基础知识:

首先,正如Sterling Archer在评论中建议的那样,您应该使用局部变量而不是全局变量,因此在它们前面添加let

执行此操作的performance gains可能是可以忽略的,但这并不是改变它的唯一原因:使用全局变量被认为是一种不好的做法,因为它们会污染全局命名空间并使您的代码更难以使用维持:Why are global variables considered bad practice?

⛓️记住指数:

您可以使用前一个迭代值计算当前迭代值,而不是在每次迭代时执行Math.pow(1.2, b + i),因为乘法应该比取幂快得多:

let exp = Math.pow(1.2, b);

for (let i = 1; i < c; ++i) {
    exp  *= 1.2;
    rv += Math.floor(a * exp);
}

此技术称为memoization

具有按位NOT运算符(〜)的楼层:

如果a为正数且您所在的值始终为< 2147483648,则可以使用bitwise NOT (~)代替Math.floor(),如下所示:

&#13;
&#13;
console.log(`Math.floor(4.01)  =  ${ Math.floor(4.01) }`);
console.log(`Math.floor(4.99)  =  ${ Math.floor(4.99) }`);
console.log(`Math.floor(-4.01) = ${ Math.floor(-4.01) }`);
console.log(`Math.floor(-4.99) = ${ Math.floor(-4.99) }`);

console.log(`~~4.01  =  ${ ~~4.01 }`);
console.log(`~~4.99  =  ${ ~~4.99 }`);
console.log(`~~-4.01 = ${ ~~-4.01 }`);
console.log(`~~-4.99 = ${ ~~-4.99 }`);

console.log(`Math.floor(2147483647.99) = ${ Math.floor(2147483647.99) }`);
console.log(`Math.floor(2147483648.01) = ${ Math.floor(2147483648.01) }`);
console.log(`~~2147483647.99 = ${ ~~2147483647.99 }`);
console.log(`~~2147483648.01 = ${ ~~2147483648.01 }`);
&#13;
.as-console-wrapper {
  max-height: 100vh !important;
}
&#13;
&#13;
&#13;

但是,如果尝试置位值>= 2147483648~~将换行并返回不正确的值,因为按位运算符使用32位整数,因此可以安全使用的最大值是2 31 -1,或2147483647

使用按位运算符替代的地板:

在这种情况下,您可以使用Math.trunc()代替Math.floor(),这会更快一些,您可以在此处看到:https://jsperf.com/number-truncating-methods/1

使用左移运算符的基数2指数:

同样,如果b是整数s.t. 1 <= b <= 30,您可以使用left shift (<<)代替第一个Math.pow(2, b)2 << (b - 1)

&#13;
&#13;
for (let i = 0; i <= 31; ++i) {
  console.log(`Math.pow(2, ${ i }) === 2 << (${ i } - 1)? ${ Math.pow(2, i) === 2 << (i - 1) ? 'Yes' : 'No'}.` );
}
&#13;
.as-console-wrapper {
  max-height: 100vh !important;
}
&#13;
&#13;
&#13;

➰使用while循环:

您是否注意到在应用了memoization技术之后我们不再使用循环内的i变量了?您现在可以将for替换为while,这不会带来巨大的收益,但仍然值得一提这个选项:

while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
} 

最终结果:

总而言之,您的超高速代码如下所示:

&#13;
&#13;
function original(a, b, c) {
  rv = Math.floor(a * Math.pow(2, b));

  for (i = 1; i < c; i++) {
    rv += Math.floor(a * Math.pow(1.2, b+i));
  }

  return rv;
}

function faster(a, b, c) {
  let rv = ~~(a * (2 << (b - 1)));
  let exp = Math.pow(1.2, b);
  
  while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
  } 

  return rv;
}

const expected = original(2, 2, 113);
const actual = faster(2, 2, 113);
const ok = expected === actual ;


if (ok) {
  // BEFORE:
  
  const t0b = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    original(2, 2, 113);
  }
  
  const tb = performance.now() - t0b;
  
  // AFTER:
  
  const t0a = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    faster(2, 2, 113);
  }
  
  const ta = performance.now() - t0a;
  
  console.log(` BEFORE = ${ tb }s`);
  console.log(`  AFTER = ${ ta }s`);
  console.log(`SPEEDUP = ${ Math.round(100 * tb / ta) / 100 } = ${ Math.round((1 - ta / tb) * 10000) / 100 }% faster!`);
  
} else {
  console.log(`Faster version = ${ actual } does not return the same as before = ${ expected }`);
}
&#13;
.as-console-wrapper {
  max-height: 100vh !important;
}
&#13;
&#13;
&#13;

循环展开:

值得一提的是,因为你要对每个操作的结果进行底层处理,这种技术不会过多地加速代码,所以可能它不值得,考虑到它的冗长程度代码变成了。

您可以阅读有关循环展开here的更多信息。

但是,如果您没有对结果进行拼接,则可以节省许多计算:

function faster (a, b, c) {
    let rv = a * (2 << (b - 1));
    let exp = Math.pow(1.2, b);

    const r = c % 4;

    if (r === 0) {
        exp *= 1.728;
        rv += a * a * a * exp;
    } else if (r === 1) {
        c += 3;
    } else if (r === 2) {
        exp *= 1.2;
        rv += a * exp;
        c += 2;
    } else if (r === 3) {
        exp *= 1.44;
        rv += a * a * exp;
        c += 1;
    }

    a = Math.pow(a, 4);
    c /= 4;

    while (--c) {
        exp *= 2.0736;
        rv += a * exp;
    }

    return rv;
}

正如您所看到的,优势在于您可以将多个迭代的计算组合在一个迭代中,而不是仅仅复制它们,就像在原始代码中一样。仅用于演示目的:

&#13;
&#13;
function original(a, b, c) {
  rv = Math.floor(a * Math.pow(2, b));

  for (i = 1; i < c; i++) {
    rv += Math.floor(a * Math.pow(1.2, b+i));
  }

  return rv;
}

function faster(a, b, c) {
  let rv = ~~(a * (2 << (b - 1)));
  let exp = Math.pow(1.2, b);
  
  const r = c % 4;
  
  if (r === 0) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
  } else if (r === 1) {
    c += 3;
  } else if (r === 2) {
    exp *= 1.2;
    rv += ~~(a * exp);
    c += 2;
  } else if (r === 3) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    c += 1;
  }
  
  c /= 4;
  
  while (--c) {
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
    exp *= 1.2;
    rv += ~~(a * exp);
  }

  return rv;
}

const expected = original(2, 2, 113);
const actual = faster(2, 2, 113);
const ok = expected === actual;

if (ok) {
  // BEFORE:
  
  const t0b = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    original(2, 2, 113);
  }
  
  const tb = performance.now() - t0b;
  
  // AFTER:
  
  const t0a = performance.now();
  
  for (let i = 0; i < 100000; ++i) {
    faster(2, 2, 113);
  }
  
  const ta = performance.now() - t0a;
  
  console.log(` BEFORE = ${ tb }s`);
  console.log(`  AFTER = ${ ta }s`);
  console.log(`SPEEDUP = ${ Math.round(100 * tb / ta) / 100 } = ${ Math.round((1 - ta / tb) * 10000) / 100 }% faster!`);
  
} else {
  console.log(`Faster version = ${ actual } does not return the same as before = ${ expected }`);
}
&#13;
.as-console-wrapper {
  max-height: 100vh !important;
}
&#13;
&#13;
&#13;

☝️提示:

另外,在发布其他问题之前,您应先阅读https://stackoverflow.com/help/how-to-ask,以便人们不会将其投票。

答案 1 :(得分:0)

如果您担心计算大范围的所有1.2 ^(b + i),请认为底层可能会在编译器优化中推断出前一层的结果。但是,如果你想明确地帮助他,你可以做类似

的事情
function x (a, b, c) {
  var rv = Math.floor(a * Math.pow(2, b))
  var multiplier = Math.pow(1.2, b + 1)
  for (i = 1; i < c; i++) {
    rv += Math.floor(a * multiplier);
    multiplier *= 1.2
  }
  return rv;
}

只是数学。