在条件下测试多个a< = b< = c的最有效方法

时间:2017-11-09 23:53:17

标签: javascript performance math optimization

我有多个数字(至少3个)来测试通过下限和上限获得的范围(我可以肯定lower <= upper条件总是满足。)

现在有较低的[1 ... n],x [1 ... n]和较高的[1 ... n] 我的目标是,我希望优化性能......

我知道,在看了this other Q&A here on StackOverflow之后,我可以选择

(unsigned)(x[n]-lower[n]) <= (upper[n]-lower[n])表格

经典&#34;经典&#34; lower[n] <= x[n] && x[n] <= upper[n]

我需要在这个场合使用JavaScript,我知道我可以获得相同的&#34;技巧&#34;使用以下语言:

我非常确定第二种方法对性能的影响最大,所以在离开时将其排除在外,只考虑第一种方法,我可以这样做:

// Example 1
function everything_is_in_range(lower, x, upper) {
    return ( ((x[0]-lower[0]) >>> 0) <= (upper[0]-lower[0]) && 
             ((x[1]-lower[1]) >>> 0) <= (upper[1]-lower[1]) &&
             ...
             ((x[n-1]-lower[n-1]) >>> 0) <= (upper[n-1]-lower[n-1]) );
}

或者我能做到:

// Example 2
function everything_is_in_range(lower, x, upper) {
    if ( ((x[0]-lower[0]) >>> 0) > (upper[0]-lower[0]) ) return false; 
    if ( ((x[1]-lower[1]) >>> 0) > (upper[1]-lower[1]) ) return false; 
    ...
    return ( ((x[n-1]-lower[n-1]) >>> 0) <= (upper[n-1]-lower[n-1]) );
}

我的问题是:

  • 无符号移位在一般性能上是不方便的,所以我应该留在&#34; classic&#34; lower[n] <= x[n] && x[n] <= upper[n]形式用于此目的?

  • 如果我的第一个答案的答案是否定的,哪种方式最有效?但更重要的是:你知道一个更好的建议吗?

P.S。我知道我可以通过以下方式处理for循环:

// Loop example
function everything_is_in_range(lower, x, upper) {
    for (i=0; i<n; ++i) if ( ((x[i]-lower[i]) >>> 0) > (upper[i]-lower[i]) ) return false; 
    return true;
}

但是

  1. 让我编写更少的代码只是方便,但它最终与第二种代码方法非​​常相似,不是吗?

  2. 我不想使用此表单,因为所有值都可能碰巧作为单个分隔参数传递(这是我的实际情况,其中我处理的是3或4个数字+绑定范围变量集,我不能改变这个)而不是数值数组(如本例所示)。

2 个答案:

答案 0 :(得分:0)

我做到了:

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
<body>
<script>

// Loop example
function everything_is_in_range(lower, x, upper) {
    for (i=0; i<3; ++i) if ( ((x[i]-lower[i]) >>> 0) > (upper[i]-lower[i]) ) return false; 
    return true;
}

// E.2
function everything_is_in_range_2(lower, x, upper) {
    if ( ((x[0]-lower[0]) >>> 0) > (upper[0]-lower[0]) ) return false; 
    if ( ((x[1]-lower[1]) >>> 0) > (upper[1]-lower[1]) ) return false; 
    return ( ((x[2]-lower[2]) >>> 0) <= (upper[2]-lower[2]) );
}

// E.1
function everything_is_in_range_1(lower, x, upper) {
    return ( ((x[0]-lower[0]) >>> 0) <= (upper[0]-lower[0]) && 
             ((x[1]-lower[1]) >>> 0) <= (upper[1]-lower[1]) &&
             ((x[2]-lower[2]) >>> 0) <= (upper[2]-lower[2]) );
}

// Loop C example
function everything_is_in_range_C(lower, x, upper) {
    for (i=0; i<3; ++i) if ( lower[i] > x[i] || x[i] > upper[i] ) return false; 
    return true;
}

// E.C2
function everything_is_in_range_C2(lower, x, upper) {
    if ( lower[0] > x[0] || x[0] > upper[0] ) return false; 
    if ( lower[1] > x[1] || x[1] > upper[1] ) return false; 
    return ( lower[2] <= x[2] && x[2] <= upper[2] );
}

// E.C1
function everything_is_in_range_C1(lower, x, upper) {
    return ( lower[0] <= x[0] && x[0] <= upper[0] && 
             lower[1] <= x[1] && x[1] <= upper[1] && 
             lower[2] <= x[2] && x[2] <= upper[2] );
}

let u = [50, 100, 150],
    x = [100, 100, 100],
    l = [25, 100, 125];

var t0, t1, m, m1, m2, mc, mc1, mc2, r;
m = m1 = m2 = mc = mc1 = mc2 = 0;
for (r=0; r < 100; ++r) {
//console.log("Round " + (r+1) + ":");

t0 = performance.now();
everything_is_in_range_1(l, x, u);
t1 = performance.now();
//console.log("Call 1 " + (t1 - t0) + " ms.");
m1 += (t1 - t0);

t0 = performance.now();
everything_is_in_range_2(l, x, u);
t1 = performance.now();
//console.log("Call 2 " + (t1 - t0) + " ms.");
m2 += (t1 - t0);

t0 = performance.now();
everything_is_in_range(l, x, u);
t1 = performance.now();
//console.log("Call loop " + (t1 - t0) + " ms.");
m += (t1 - t0);

t0 = performance.now();
everything_is_in_range_C1(l, x, u);
t1 = performance.now();
//console.log("Call C1 " + (t1 - t0) + " ms.");
mc1 += (t1 - t0);

t0 = performance.now();
everything_is_in_range_C2(l, x, u);
t1 = performance.now();
//console.log("Call C2 " + (t1 - t0) + " ms.");
mc2 += (t1 - t0);

t0 = performance.now();
everything_is_in_range_C(l, x, u);
t1 = performance.now();
//console.log("Call loop C " + (t1 - t0) + " ms.");
mc += (t1 - t0);
}

console.log("------------- AVERAGE RESULTS (after " + r + " rounds)-------------");
console.log("1: " + (m1 / r) + " ms.");
console.log("2: " + (m2 / r) + " ms.");
console.log("Loop: " + (m / r) + " ms.");
console.log("C1: " + (mc1 / r) + " ms.");
console.log("C2: " + (mc2 / r) + " ms.");
console.log("Loop C: " + (mc / r) + " ms.");

</script>
</body>
</html>

我把这个&#34;棘手的&#34;和#34;经典&#34;形式(包括循环),我明白了:

------------- AVERAGE RESULTS (after 100 rounds)-------------
1: 0.0017500000000001137 ms.  
2: 0.0014500000000009549 ms. 
Loop: 0.002749999999998636 ms. 
C1: 0.0014500000000003865 ms. 
C2: 0.001150000000000091 ms.  
Loop C: 0.0019000000000011141 ms. 

所以...似乎最好的JavaScript方式是&#34; classic&#34;形式,特别是这个&#34;详细&#34;方式似乎比循环方式更好:

function everything_is_in_range_C2(lower, x, upper) {
    if ( lower[0] > x[0] || x[0] > upper[0] ) return false; 
    if ( lower[1] > x[1] || x[1] > upper[1] ) return false; 
    return ( lower[2] <= x[2] && x[2] <= upper[2] );
}

嗯,我认为当有大量的数字需要测试时,所有已经在数组中的所有代码都在努力编写所有代码而不是利用循环它不值得在性能上获得轻微的收益......

答案 1 :(得分:0)

优化的要点不是unsigned部分,而是最小化分支错误预测。

如果您有机会在Stack Overflow上看到最多投票的问题和回答: Why is it faster to process a sorted array than an unsorted array?

&#13;
&#13;
function f1(l, x, u) { if ( l[0] > x[0] || x[0] > u[0] ) return false; 
                       if ( l[1] > x[1] || x[1] > u[1] ) return false; 
                       return ( l[2] <= x[2] && x[2] <= u[2] ); }

function f2(l, x, u) { return !!( ( x[0] - u[0] ^ x[0] - l[0] ) & 
                                  ( x[1] - u[1] ^ x[1] - l[1] ) &
                                  ( x[2] - u[2] ^ x[2] - l[2] ) ) }

let l = [1, 1, 1], x = [2, 2, 2], u = [3, 3, 3], t, b, p = performance, c = 12345678
t = p.now(); for (let i = c; i--;) b = f1(l, x, u); t = p.now() - t; console.log(1, t|0, b)
t = p.now(); for (let i = c; i--;) b = f2(l, x, u); t = p.now() - t; console.log(2, t|0, b)

l = [1, 2, 3], x = [2, 2, 2], u = [3, 3, 3]
t = p.now(); for (let i = c; i--;) b = f1(l, x, u); t = p.now() - t; console.log(1, t|0, b)
t = p.now(); for (let i = c; i--;) b = f2(l, x, u); t = p.now() - t; console.log(2, t|0, b)

l = [3, 3, 3], x = [2, 2, 2], u = [3, 3, 3]
t = p.now(); for (let i = c; i--;) b = f1(l, x, u); t = p.now() - t; console.log(1, t|0, b)
t = p.now(); for (let i = c; i--;) b = f2(l, x, u); t = p.now() - t; console.log(2, t|0, b)
&#13;
&#13;
&#13;

正如您可能已经注意到的那样,没有比较的无分支版本在平均时间上看起来要快一些,但是当比较版本可以提前退出时会慢一些。

请注意,无分支版本可能不正确(绝对不正确的负数),因为它主要是试错的结果,我没有进行大量测试。

这两个版本都可以使用SIMD说明进行优化,但只有few browsers支持。