web-dev-qa-db-ja.com

整数が2つの整数の間(両端を含む)にあるかどうかを判断するための最も早い方法

整数が2つの整数の間にあるかどうかをテストするCまたはC++のx >= start && x <= endより速い方法はありますか?

UPDATE:私の特定のプラットフォームはiOSです。これは、ピクセルを特定の正方形内の円に制限するボックスぼかし機能の一部です。

UPDATE受け入れられた答え を試した後、私はそれを通常のx >= start && x <= endのやり方でするよりも1行のコードで桁違いのスピードアップを得ました。

UPDATE:これはXCodeのアセンブラを使った前後のコードです。

NEW WAY

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

オールドウェイ

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

分岐を減らしたりなくしたりすることで、このような劇的なスピードアップが得られることは驚くべきことです。

374
jjxtra

たった1つの比較/ブランチでこれを行うための古いトリックがあります。それが本当にスピードを向上させるかどうかは疑問の余地があるかもしれません、そしてたとえそれがたとえそうであったとしても、おそらく気づかないか気にするのはあまりにも少ないです、しかし2つの比較から始めているとき、大きな改善の可能性はかなり遠いです。コードは次のようになります。

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

現代の典型的なコンピュータ(つまり、2の補数を使用するもの)では、unsignedへの変換は本当に簡単です - 同じビットがどのように表示されるかが変わるだけです。

一般的な場合では、(推定された)ループの外側でupper-lowerを事前計算することができるので、通常はそれほど時間がかかりません。分岐命令の数を減らすことに加えて、これはまた(一般的に)分岐予測を改善する。この場合、数値が範囲の下端よりも下でも上端よりも上にあるかにかかわらず、同じ分岐が行われます。

これがどのように機能するかについては、基本的な考え方は非常に単純です。符号なしの数として表示すると、負の数は、正の数として始まるものよりも大きくなります。

実際には、このメソッドはnumberと区間を原点に変換し、numberが区間[0, D]にあるかどうかを確認します。ここで、D = upper - lowernumberが下限を下回る場合:負の値、上限を超えた場合:Dよりも大きい

513
Jerry Coffin

そのような小規模でコードを大幅に最適化することは稀です。パフォーマンスが大幅に向上するのは、コードをより高いレベルから観察して変更することによるものです。範囲テストの必要性を完全に排除するか、またはO(n ^ 2)の代わりにそれらのうちO(n)のみを実行することができます。不等式の一方が常に暗示されるようにテストを並べ替えることができます。アルゴリズムが理想的であっても、このコードが1000万回の範囲テストを行う方法を見て、それらをまとめてSSEを使用して多数のテストを並行して実行する方法を見つけると、利益が得られます。 。

18
Ben Jackson

同じデータに対して何回テストを実行するかによって異なります。

テストを1回実行しているのであれば、アルゴリズムを高速化する意味のある方法はおそらくないでしょう。

非常に限られた値のセットに対してこれを行っている場合は、ルックアップテーブルを作成できます。索引付けを実行する方がコストがかかるかもしれませんが、テーブル全体をキャッシュに収めることができれば、コードからすべての分岐を削除できるため、処理が速くなります。

あなたのデータでは、ルックアップテーブルは128 ^ 3 = 2,097,152となります。 3つの変数のうちの1つを制御できるので、一度にstart = Nが存在するすべてのインスタンスを考慮すると、ワーキングセットのサイズは128^2 = 16432バイトに減少します。これは、最新のほとんどのキャッシュに適しています。

実際のコードをベンチマークして、ブランチレスルックアップテーブルが明らかな比較よりも十分に速いかどうかを確認する必要があります。

17
Andrew Prock

この答えは、受け入れられた答えで行われたテストについて報告することです。ソートされた乱数整数の大きなベクトルに対して閉範囲テストを実行しました。驚いたことに、(low <= num && num <= high)の基本的な方法は、実際には上記の一般的な回答よりも高速です。テストは、HP Pavilion g6(6GB RAM搭載のAMD A6-3400APU)で行いました。テストに使用したコアコードは次のとおりです。

int num = Rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

上記の受け入れられた答えである以下と比較してください。

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

RandVecはソートされたベクトルであることに注意してください。 MaxNumのサイズに関係なく、最初の方法は私のマシンの2番目の方法よりも優れています。

2
rezeli