【问题标题】:Most efficient way to test multiple a <= b <=c in a condition在一个条件下测试多个 a <= b <=c 的最有效方法
【发布时间】:2017-11-09 23:53:16
【问题描述】:

我有多个数字(至少 3 个),每个数字都可以针对由下限和上限获得的范围进行测试(我可以确定始终满足 lower &lt;= upper 条件)。

现在有 lower[1...n]、x[1...n] 和 upper[1...n] 我的目标是优化性能...

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

(unsigned)(x[n]-lower[n]) &lt;= (upper[n]-lower[n]) 表格

超越“经典”lower[n] &lt;= x[n] &amp;&amp; x[n] &lt;= upper[n]

在这种情况下我需要使用 JavaScript,并且我知道我可以使用这种语言获得相同的“技巧”:

我很确定第二种方法会对性能产生最严重的影响,因此,在出发时将其排除在外,并且仅考虑第一种方法,我可以这样做:

// 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]) );
}

我的问题是:

  • 无符号移位在一般性能上是否不方便,所以我应该保留“经典”lower[n] &lt;= x[n] &amp;&amp; x[n] &lt;= upper[n] 形式?

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


附:我知道我可以通过以下方式使用 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 个数字 + 绑定范围集变量,我无法更改它)而不是值数组(如本例中)。

【问题讨论】:

  • Javascript 没有无符号数字的概念,因此您将无法利用该特殊技巧
  • function withinRange(num, begin, end){ if(num &gt;= begin &amp;&amp; num &lt;= end){ return true; } else{ return false; } }
  • 将其设为“无符号”很可能会因为额外的转换步骤 stackoverflow.com/questions/14890994/… 而让它变慢一点,但我的猜测是函数调用本身会比比较有更大的开销跨度>
  • @PHPglue :我知道这种“经典”方式(顺便说一句return (num &gt;= begin &amp;&amp; num &lt;= end) 会更好)但是,无论如何,我在问是否有可能对此进行优化,而且,我有多个在相同条件下测试的数字。
  • @Hamms :是的,我知道我不能应用相同的 C/C++ 技巧,但我也知道可以使用 Uint32Array(但我担心这会矫枉过正关于表演)或&gt;&gt;&gt; 0 技巧...我认为与“经典”方式相比,这是唯一的方法(我现在编辑了我的问题)

标签: javascript performance math optimization


【解决方案1】:

我做了这个:

<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>

我将“棘手”和“经典”形式(包括循环)都放入其中,我得到了:

------------- 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 中最好的方式是“经典”形式,特别是,这种“冗长”的方式似乎比循环方式更好:

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] );
}

好吧,我认为当有很多数字要测试并且所有内容都已经在数组中时,编写所有代码而不是利用循环的努力无论如何都不值得稍微提高性能......

【讨论】:

    【解决方案2】:

    优化的重点不是unsigned部分,而是尽量减少分支误判。

    如果您还没有在 Stack Overflow 上看到投票率最高的问题和答案:
    Why is it faster to process a sorted array than an unsorted array?

    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)

    您可能会注意到,没有比较的无分支版本在时间相当恒定的情况下平均看起来要快一些,但当比较版本可以提前退出时,速度会慢一些。

    请注意,无分支版本可能不正确(负数肯定不正确),因为它主要是反复试验的结果,我没有进行广泛的测试。

    两个版本都可以使用SIMD指令进行优化,但它们仅在few browsers中受支持。

    【讨论】:

      猜你喜欢
      • 2021-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多