特定の塩基に対して標準の%演算子よりも速い整数モジュラスを作成するためのトリックはありますか?
私のプログラムでは、約1000〜4000(n%2048など)を探しています。単にn%2048
よりもnモジュラス2048を実行するより速い方法はありますか?
2048の例のように、コンパイル時に分母が2の累乗であることがわかっている場合は、1を減算してビット単位で実行できます。
あれは:
n % m == n & (m - 1)
...ここで、m
は2の累乗です。
例えば:
22 % 8 == 22 - 16 == 6
Dec Bin
----- -----
22 = 10110
8 = 01000
8 - 1 = 00111
22 & (8 - 1) = 10110
& 00111
-------
6 = 00110
優れたコンパイラには、%
に対する独自の最適化があり、上記の手法と同じくらい高速である可能性があることに注意してください。算術演算子はかなり高度に最適化される傾向があります。
2の累乗_2^n
_の場合、最後のn
ビットを除くすべてのビットをゼロにするだけです。
例(32ビット整数を想定):
_x%2
_は_x & 0x00000001
_と同等です
_x%4
_は_x & 0x00000003
_と同等です
一般に、x % (2^n)
はx & (2^n-1)
と同じです。 Cで記述されている場合、これはx & ((1<<n)-1)
になります。
これは、_2^n
_が_n+1
_番目のビット(右から)に1を与えるためです。したがって、_2^n-1
_は、右側にn
を、左側に0を与えます。
ここにいくつかのテクニックがあります モジュラス演算を複製します。
ベンチマークされたものの中で、これが最速でした(2048シナリオに合うように変更されました)。あなたの「最大」が数百万でなく、あなたが言及した1000-4000の範囲である限り、それはあなたにとってもより速く働くかもしれません:
int threshold = 2048; //the number to mod by
int max = 1000; //the number on the left. Ex: 1000 % 2048
int total = 0;
int y = 0;
for (int x = 0; x < max; x++)
{
if (y > (threshold - 1))
{
y = 0;
total += x;
}
y += 1;
}
return total;
試してごらん。それは より速く実行されました さまざまな設定で作者のマシン上で実行されるので、あなたにとっても見事に実行されるはずです。
上位ビットをゼロにすることができます。
x = 11 = 1011
x%4 = 3 = 0011
したがって、x%4の場合、最後の2ビットを取ることができます-負の数が使用された場合にどうなるかはわかりませんが
ブランチレスnon-2のべき乗のモジュラスは、実行時に マジック定数の事前計算 によって可能であり、乗算-加算-シフト。
これは、Intel Corei5に組み込まれているモジュロ演算子%
よりも約2倍高速です。
X86 CPU div
命令は、mul
は3サイクルで、ビット単位のopsはそれぞれ1サイクルです。
以下に示す概念実証とタイミング。 series_len
は、単一の変数に対して直列に実行されるモジュラス演算の数を示します。これは、CPUが並列化によってレイテンシーを隠すのを防ぐためです。
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
typedef int32_t s32;
typedef uint32_t u32;
typedef uint64_t u64;
#define NUM_NUMS 1024
#define NUM_RUNS 500
#define MAX_NUM UINT32_MAX
#define MAX_DEN 1024
struct fastdiv {
u32 mul;
u32 add;
s32 shift;
u32 _odiv; /* save original divisor for modulo calc */
};
static u32 num[NUM_NUMS];
static u32 den[NUM_NUMS];
static struct fastdiv fd[NUM_NUMS];
/* hash of results to prevent gcc from optimizing out our ops */
static u32 cookie = 0;
/* required for magic constant generation */
u32 ulog2(u32 v) {
u32 r, shift;
r = (v > 0xFFFF) << 4; v >>= r;
shift = (v > 0xFF ) << 3; v >>= shift; r |= shift;
shift = (v > 0xF ) << 2; v >>= shift; r |= shift;
shift = (v > 0x3 ) << 1; v >>= shift; r |= shift;
r |= (v >> 1);
return r;
}
/* generate constants for implementing a division with multiply-add-shift */
void fastdiv_make(struct fastdiv *d, u32 divisor) {
u32 l, r, e;
u64 m;
d->_odiv = divisor;
l = ulog2(divisor);
if (divisor & (divisor - 1)) {
m = 1ULL << (l + 32);
d->mul = (u32)(m / divisor);
r = (u32)m - d->mul * divisor;
e = divisor - r;
if (e < (1UL << l)) {
++d->mul;
d->add = 0;
} else {
d->add = d->mul;
}
d->shift = l;
} else {
if (divisor == 1) {
d->mul = 0xffffffff;
d->add = 0xffffffff;
d->shift = 0;
} else {
d->mul = 0x80000000;
d->add = 0;
d->shift = l-1;
}
}
}
/* 0: use function that checks for a power-of-2 modulus (speedup for POTs)
* 1: use inline macro */
#define FASTMOD_BRANCHLESS 0
#define fastdiv(v,d) ((u32)(((u64)(v)*(d)->mul + (d)->add) >> 32) >> (d)->shift)
#define _fastmod(v,d) ((v) - fastdiv((v),(d)) * (d)->_odiv)
#if FASTMOD_BRANCHLESS
#define fastmod(v,d) _fastmod((v),(d))
#else
u32 fastmod(u32 v, struct fastdiv *d) {
if (d->mul == 0x80000000) {
return (v & ((1 << d->shift) - 1));
}
return _fastmod(v,d);
}
#endif
u32 random32(u32 upper_bound) {
return arc4random_uniform(upper_bound);
}
u32 random32_range(u32 lower_bound, u32 upper_bound) {
return random32(upper_bound - lower_bound) + lower_bound;
}
void fill_arrays() {
int i;
for (i = 0; i < NUM_NUMS; ++i) {
num[i] = random32_range(MAX_DEN, MAX_NUM);
den[i] = random32_range(1, MAX_DEN);
fastdiv_make(&fd[i], den[i]);
}
}
void fill_arrays_pot() {
u32 log_bound, Rand_log;
int i;
log_bound = ulog2(MAX_DEN);
for (i = 0; i < NUM_NUMS; ++i) {
num[i] = random32_range(MAX_DEN, MAX_NUM);
Rand_log = random32(log_bound) + 1;
den[i] = 1 << Rand_log;
fastdiv_make(&fd[i], den[i]);
}
}
u64 clock_ns() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000000000 + tv.tv_usec*1000;
}
void use_value(u32 v) {
cookie += v;
}
int main(int argc, char **arg) {
u64 builtin_npot_ns;
u64 builtin_pot_ns;
u64 branching_npot_ns;
u64 branching_pot_ns;
u64 branchless_npot_ns;
u64 branchless_pot_ns;
u64 t0, t1;
u32 v;
int s, r, i, j;
int series_len;
builtin_npot_ns = builtin_pot_ns = 0;
branching_npot_ns = branching_pot_ns = 0;
branchless_npot_ns = branchless_pot_ns = 0;
for (s = 5; s >= 0; --s) {
series_len = 1 << s;
for (r = 0; r < NUM_RUNS; ++r) {
/* built-in NPOT */
fill_arrays();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v /= den[i];
}
use_value(v);
}
t1 = clock_ns();
builtin_npot_ns += (t1 - t0) / NUM_NUMS;
/* built-in POT */
fill_arrays_pot();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v /= den[i];
}
use_value(v);
}
t1 = clock_ns();
builtin_pot_ns += (t1 - t0) / NUM_NUMS;
/* branching NPOT */
fill_arrays();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v = fastmod(v, fd+i);
}
use_value(v);
}
t1 = clock_ns();
branching_npot_ns += (t1 - t0) / NUM_NUMS;
/* branching POT */
fill_arrays_pot();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v = fastmod(v, fd+i);
}
use_value(v);
}
t1 = clock_ns();
branching_pot_ns += (t1 - t0) / NUM_NUMS;
/* branchless NPOT */
fill_arrays();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v = _fastmod(v, fd+i);
}
use_value(v);
}
t1 = clock_ns();
branchless_npot_ns += (t1 - t0) / NUM_NUMS;
/* branchless POT */
fill_arrays_pot();
t0 = clock_ns();
for (i = 0; i < NUM_NUMS; ++i) {
v = num[i];
for (j = 0; j < series_len; ++j) {
v = _fastmod(v, fd+i);
}
use_value(v);
}
t1 = clock_ns();
branchless_pot_ns += (t1 - t0) / NUM_NUMS;
}
builtin_npot_ns /= NUM_RUNS;
builtin_pot_ns /= NUM_RUNS;
branching_npot_ns /= NUM_RUNS;
branching_pot_ns /= NUM_RUNS;
branchless_npot_ns /= NUM_RUNS;
branchless_pot_ns /= NUM_RUNS;
printf("series_len = %d\n", series_len);
printf("----------------------------\n");
printf("builtin_npot_ns : %llu ns\n", builtin_npot_ns);
printf("builtin_pot_ns : %llu ns\n", builtin_pot_ns);
printf("branching_npot_ns : %llu ns\n", branching_npot_ns);
printf("branching_pot_ns : %llu ns\n", branching_pot_ns);
printf("branchless_npot_ns : %llu ns\n", branchless_npot_ns);
printf("branchless_pot_ns : %llu ns\n\n", branchless_pot_ns);
}
printf("cookie=%u\n", cookie);
}
Intel Core i5(MacBookAir7,2)、macOS 10.11.6、clang 8.0.0
series_len = 32
----------------------------
builtin_npot_ns : 218 ns
builtin_pot_ns : 225 ns
branching_npot_ns : 115 ns
branching_pot_ns : 42 ns
branchless_npot_ns : 110 ns
branchless_pot_ns : 110 ns
series_len = 16
----------------------------
builtin_npot_ns : 87 ns
builtin_pot_ns : 89 ns
branching_npot_ns : 47 ns
branching_pot_ns : 19 ns
branchless_npot_ns : 45 ns
branchless_pot_ns : 45 ns
series_len = 8
----------------------------
builtin_npot_ns : 32 ns
builtin_pot_ns : 34 ns
branching_npot_ns : 18 ns
branching_pot_ns : 10 ns
branchless_npot_ns : 17 ns
branchless_pot_ns : 17 ns
series_len = 4
----------------------------
builtin_npot_ns : 15 ns
builtin_pot_ns : 16 ns
branching_npot_ns : 8 ns
branching_pot_ns : 3 ns
branchless_npot_ns : 7 ns
branchless_pot_ns : 7 ns
series_len = 2
----------------------------
builtin_npot_ns : 8 ns
builtin_pot_ns : 7 ns
branching_npot_ns : 4 ns
branching_pot_ns : 2 ns
branchless_npot_ns : 2 ns
branchless_pot_ns : 2 ns
符号なし整数を乗算/除算する最も速い方法は、それらを左または右にビットシフトすることです。シフト演算はCPUコマンドと直接一致します。たとえば、3 << 2 = 6、4 >> 1 = 2です。
同じトリックを使用してモジュールを計算できます。整数を左に十分にシフトして剰余ビットのみを残し、次にそれを右にシフトして剰余値を確認できるようにします。
一方、CPUコマンドとして整数モジュロも存在します。整数モジュロ演算子が最適化されたビルドでこのコマンドにマップされている場合、ビットシフトトリックを使用しても改善は見られません。
次のコードは、最後の2ビットだけが残るように十分にシフトすることによって7%4を計算します(4 = 2 ^ 2以降)。これは、30ビットをシフトする必要があることを意味します。
uint i=7;
var modulo=((i<<30)>>30);
結果は3です
編集:
単に上位ビットを消去することを提案しているすべてのソリューションを読んだだけです。同じ効果がありますが、はるかに単純で直接的なものです。
2の累乗であるリテラルで除算する場合、答えはおそらく「いいえ」です。適切なコンパイラーは、そのような式を自動的にAND演算のバリエーションに変換します。これは、最適にかなり近いものです。