%演算子よりも速く整除テスト?

DaBler:

私は自分のコンピュータ上で好奇心旺盛なことに気づきました。*手書き整除テストが大幅に高速化よりも%オペレータ。最小限の例を考えてみます。

* AMD Ryzen Threadripper 2990WX、GCC 9.2.0

static int divisible_ui_p(unsigned int m, unsigned int a)
{
    if (m <= a) {
        if (m == a) {
            return 1;
        }

        return 0;
    }

    m += a;

    m >>= __builtin_ctz(m);

    return divisible_ui_p(m, a);
}

例は奇数によって制限されるam > 0しかし、それは簡単にすべてに一般化することができるamコードは、単に追加の一連の分割を変換します。

今でコンパイルされたテストプログラムを検討してください-std=c99 -march=native -O3

    for (unsigned int a = 1; a < 100000; a += 2) {
        for (unsigned int m = 1; m < 100000; m += 1) {
#if 1
            volatile int r = divisible_ui_p(m, a);
#else
            volatile int r = (m % a == 0);
#endif
        }
    }

...と私のコンピュータ上の結果:

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.52user |
| builtin % operator |   17.61user |

したがって、2倍以上速いです。

質問:あなたは私にあなたのマシン上でどのようにコードの振る舞いを伝えることはできますか?それはGCCで最適化の機会を逃しましたか?あなたも速く、このテストを行うことができますか?


UPDATE:としては、ここで、要求された最小限の再現性の例です。

#include <assert.h>

static int divisible_ui_p(unsigned int m, unsigned int a)
{
    if (m <= a) {
        if (m == a) {
            return 1;
        }

        return 0;
    }

    m += a;

    m >>= __builtin_ctz(m);

    return divisible_ui_p(m, a);
}

int main()
{
    for (unsigned int a = 1; a < 100000; a += 2) {
        for (unsigned int m = 1; m < 100000; m += 1) {
            assert(divisible_ui_p(m, a) == (m % a == 0));
#if 1
            volatile int r = divisible_ui_p(m, a);
#else
            volatile int r = (m % a == 0);
#endif
        }
    }

    return 0;
}

でコンパイルされたgcc -std=c99 -march=native -O3 -DNDEBUGとAMD Ryzen Threadripper 2990WXに

gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0

アップデート2:としては、任意の処理できるバージョン要求されたam(をあなたも整数オーバーフローを避けたい場合は、テストは倍の長入力整数として整数型で実装する必要があります):

int divisible_ui_p(unsigned int m, unsigned int a)
{
#if 1
    /* handles even a */
    int alpha = __builtin_ctz(a);

    if (alpha) {
        if (__builtin_ctz(m) < alpha) {
            return 0;
        }

        a >>= alpha;
    }
#endif

    while (m > a) {
        m += a;
        m >>= __builtin_ctz(m);
    }

    if (m == a) {
        return 1;
    }

#if 1
    /* ensures that 0 is divisible by anything */
    if (m == 0) {
        return 1;
    }
#endif

    return 0;
}
DaBler:

私は私の質問を自分自身にお答えします。私が分岐予測の被害者になったようです。オペランドの相互の大きさは、唯一の彼らの順序を重要ではないと思われます。

次の実装を考えてみましょう

int divisible_ui_p(unsigned int m, unsigned int a)
{
    while (m > a) {
        m += a;
        m >>= __builtin_ctz(m);
    }

    if (m == a) {
        return 1;
    }

    return 0;
}

そして、配列

unsigned int A[100000/2];
unsigned int M[100000-1];

for (unsigned int a = 1; a < 100000; a += 2) {
    A[a/2] = a;
}
for (unsigned int m = 1; m < 100000; m += 1) {
    M[m-1] = m;
}

である/使用してシャッフルされていないシャッフル機能を。

シャッフルがなければ、結果はまだです

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |    8.56user |
| builtin % operator |   17.59user |

私はこれらの配列をシャッフルしかし、一度、結果が異なっています

| implementation     | time [secs] |
|--------------------|-------------|
| divisible_ui_p     |   31.34user |
| builtin % operator |   17.53user |

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=369052&siteId=1