私は自分のコンピュータ上で好奇心旺盛なことに気づきました。*手書き整除テストが大幅に高速化よりも%
オペレータ。最小限の例を考えてみます。
* 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);
}
例は奇数によって制限されるa
とm > 0
。しかし、それは簡単にすべてに一般化することができるa
とm
。コードは、単に追加の一連の分割を変換します。
今でコンパイルされたテストプログラムを検討してください-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:としては、任意の処理できるバージョン要求されたa
とm
(をあなたも整数オーバーフローを避けたい場合は、テストは倍の長入力整数として整数型で実装する必要があります):
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;
}
私は私の質問を自分自身にお答えします。私が分岐予測の被害者になったようです。オペランドの相互の大きさは、唯一の彼らの順序を重要ではないと思われます。
次の実装を考えてみましょう
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 |