% by a run-time divisor costs 20–90 cycles; the compiler only turns division by a compile-time constant into multiplications. When a hot loop reduces modulo a value read from input (or a 64-bit modulus in Miller–Rabin / Pollard rho), replace the division by multiplications:

ToolModulusIdea
Barrettany , run timeprecompute ; the estimated quotient is off by at most 1
Lemire fastmod, operands 64-bit fractional reciprocal, two multiplications
Montgomeryodd keep ; reduce products with REDC (no division at all)
mulmod64long double estimate of , exact correction
using u64 = unsigned long long; using u128 = __uint128_t;
struct Barrett {                                           // x % m for any x < 2^64
    u64 m, im;
    Barrett(u64 m) : m(m), im(~0ULL / m) {}
    u64 reduce(u64 x) const { u64 q = (u64)(((u128)im * x) >> 64), r = x - q * m; return r >= m ? r - m : r; }
    u64 mul(u64 a, u64 b) const { return reduce(a * b); } // a, b < m < 2^32
};
struct FastMod32 {                                         // Lemire: x % d for x, d < 2^32
    u64 M; unsigned d;
    FastMod32(unsigned d) : M(~0ULL / d + 1), d(d) {}
    unsigned reduce(unsigned x) const { u64 low = M * x; return (unsigned)(((u128)low * d) >> 64); }
};
struct Montgomery64 {                                      // odd n < 2^62, values kept as a * 2^64 mod n
    u64 n, ninv, r2;
    Montgomery64(u64 n) : n(n), ninv(n) {
        for (int i = 0; i < 6; i++) ninv *= 2 - n * ninv;  // n^{-1} mod 2^64 (Newton: bits double)
        ninv = -ninv; r2 = (u64)(-(u128)n % n);           // -n^{-1}, and 2^128 mod n
    }
    u64 redc(u128 t) const { u64 m = (u64)t * ninv; u64 r = (u64)((t + (u128)m * n) >> 64); return r >= n ? r - n : r; }
    u64 to(u64 a) const { return redc((u128)(a % n) * r2); }
    u64 val(u64 a) const { return redc(a); }
    u64 mul(u64 a, u64 b) const { return redc((u128)a * b); }   // both in Montgomery form
    u64 add(u64 a, u64 b) const { a += b; return a >= n ? a - n : a; }
    u64 pow(u64 a, u64 e) const { u64 r = to(1); for (; e; e >>= 1, a = mul(a, a)) if (e & 1) r = mul(r, a); return r; }
};
u64 mulmod64(u64 a, u64 b, u64 m) {                        // a, b < m <= 7.2e18, no __int128
    ll r = (ll)(a * b - m * (u64)(1.L / m * a * b));
    return r + m * (r < 0) - m * (r >= (ll)m);
}

Montgomery form is closed under addition and subtraction, equality is preserved (the map is a bijection), and only multiplication needs redc. Convert in once, compute many products (Miller–Rabin powers, Pollard rho iterations, NTT butterflies, RSA/ECC exponentiation — Montgomery is why modular exponentiation is fast in every crypto library), convert out once. In practice Barrett is roughly 4x faster than % by a run-time divisor, and Montgomery another 2-3x faster than Barrett when the modulus is fixed for many multiplications — the gap is this large because Barrett still does two full-width multiplications per %, while Montgomery’s redc replaces the division entirely and the conversion overhead amortizes away.

Montgomery needs an odd modulus

must be odd for to exist (the Newton iteration requires it). For an even modulus, use Barrett reduction instead, or split the even part out via CRT (CRT) and run Montgomery on the odd factor.