如何简化以下功能以及重复调用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;
}
答案 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。
如果a
为正数且您所在的值始终为< 2147483648
,则可以使用bitwise NOT (~
)代替Math.floor()
,如下所示:
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;
但是,如果尝试置位值>= 2147483648
,~~
将换行并返回不正确的值,因为按位运算符使用32位整数,因此可以安全使用的最大值是2 31 -1,或2147483647
。
在这种情况下,您可以使用Math.trunc()
代替Math.floor()
,这会更快一些,您可以在此处看到:https://jsperf.com/number-truncating-methods/1
同样,如果b
是整数s.t. 1 <= b <= 30
,您可以使用left shift (<<
)代替第一个Math.pow(2, b)
:2 << (b - 1)
:
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;
您是否注意到在应用了memoization技术之后我们不再使用循环内的i
变量了?您现在可以将for
替换为while
,这不会带来巨大的收益,但仍然值得一提这个选项:
while (--c) {
exp *= 1.2;
rv += ~~(a * exp);
}
总而言之,您的超高速代码如下所示:
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;
值得一提的是,因为你要对每个操作的结果进行底层处理,这种技术不会过多地加速代码,所以可能它不值得,考虑到它的冗长程度代码变成了。
您可以阅读有关循环展开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;
}
正如您所看到的,优势在于您可以将多个迭代的计算组合在一个迭代中,而不是仅仅复制它们,就像在原始代码中一样。仅用于演示目的:
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;
另外,在发布其他问题之前,您应先阅读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;
}
只是数学。