packages feed

atrophy-0.2.0.0: README.md

# Atrophy
Fast div/mod via arithmetic strength reduction.

Precompute a divisor once, then divide by it with a multiplication and a few
shifts instead of a hardware division.

```haskell
import Atrophy

let d = new (NonZero 7) :: StrengthReduced Word64
div' 100 d  -- 14
rem' 100 d  -- 2
```

Works for `Word8`, `Word16`, `Word32`, `Word64` and `Word128`, all through the
`StrengthReduce` class. Everything is `INLINE`, branchless where it matters, and
never allocates.

## Constants

GHC's native code generator does **not** strength-reduce division by constants:
``x `quot` 7`` compiles to a `div` instruction. `Atrophy.Known` does it for you,
computing the magic numbers during type checking:

```haskell
divK @7 x       -- one multiplication, no division
remK @1000 x
```

It also handles numerators known at compile time, with a runtime divisor:

```haskell
divN @(2 ^ 63) d          -- d :: StrengthReduced Word64; no multiplication at all
divNonZeroN @1000000 d    -- d :: NonZero Word64; a 32-bit hardware division
```

Zero, one, powers of two and `maxBound` skip the multiplication entirely, and
`Word128` numerators below 2^64 need two 64-bit multiplications instead of eight.
`divConst` and friends do the same for literal numerators passed as values; GHC
folds the branches away.

## Algorithms

* `Word64`: Granlund & Montgomery, "Division by Invariant Integers using
  Multiplication". One `mul`, no branches, no special cases for 1 or powers of
  two.
* `Word32` and smaller: Lemire, Kaser & Kurz, "Faster Remainder by Direct
  Computation". One `mul`.
* `Word128`: Granlund & Montgomery on 64-bit limbs. `new` uses hardware 128/64
  divisions and a normalized 3-by-2 division.
* Compile-time divisors: libdivide's unsigned algorithm, with the choice between
  shift, multiply-shift and multiply-add-shift made at compile time.
* `Atrophy.LongDivision`: Möller & Granlund, "Improved division by invariant
  integers", for dividing little-endian multi-limb numbers by a 64-bit divisor.

## Benchmarks

Nanoseconds per operation, averaged over 10000 uniformly random dividends.
Divisors have a uniformly random bit length. GHC 9.14.1, native code generator,
AMD Ryzen 7 7840U. "atrophy 0.1" is the previous release, run on the same data.

| `Word64`                           | GHC `quot` | atrophy 0.1 | atrophy |
|------------------------------------|-----------:|------------:|--------:|
| `new`                              |            |        5.56 |    2.92 |
| one divisor, many dividends        |       1.52 |        17.6 |    0.92 |
| one divisor, remainder             |            |             |    1.13 |
| unique divisors (`new` + `div'`)   |       1.61 |        33.5 |    2.54 |
| constant divisor 7 (`divK`)        |       1.91 |             |    0.85 |
| constant divisor 10^9+7 (`divK`)   |       1.50 |             |    0.88 |
| constant numerator 10^6, `NonZero` divisor (`divNonZeroN`) | 1.50 | | 0.52 |
| constant numerator 2^63, `NonZero` divisor (`divNonZeroN`) | 3.05 | | 1.61 |

| `Word32`                           | GHC `quot` | atrophy 0.1 | atrophy |
|------------------------------------|-----------:|------------:|--------:|
| `new`                              |            |        1.56 |    1.88 |
| one divisor, many dividends        |       1.29 |        1.05 |    0.70 |
| unique divisors (`new` + `div'`)   |       1.29 |        1.61 |    1.72 |
| constant divisor 7 (`divK`)        |       1.29 |             |    0.69 |

| `Word128`                          | wide-word `quot` | atrophy |
|------------------------------------|-----------------:|--------:|
| `new`                              |                  |    10.8 |
| one divisor, many dividends        |              121 |    4.89 |
| unchecked hardware division (`divNonZero`) |          |    4.12 |
| unique divisors (`new` + `div'`)   |             39.2 |    16.3 |
| constant divisor 10^19 (`divK`)    |             4.68 |    3.91 |
| constant numerator 10^18, `NonZero` divisor (`divNonZeroN`) | 5.13 | 0.92 |

Constant numerators with a precomputed divisor, cycling through 64 divisors:

|                                    | `div'` | `divN` |
|------------------------------------|-------:|-------:|
| `Word64`, numerator 2^63           |   2.26 |   1.78 |
| `Word64`, numerator 2^64 - 1       |   2.23 |   1.78 |
| `Word128`, numerator 10^18         |   5.40 |   2.80 |

Dividing a 64-limb number by a 64-bit divisor with `longDivision` takes 281 ns,
including allocating the quotient; GMP's hand-written assembly, via `Integer`,
takes 166 ns.

Performance is *heavily* platform dependent. Zen 4 has an unusually fast
hardware divider, so these numbers understate the gains on most other CPUs,
where a 64-bit `div` costs 35 to 90 cycles rather than 10 to 20. On this machine
a hardware division is still faster than `new` followed by a single `div'`, so
strength reduction pays off when a divisor is reused.

Run them yourself with `cabal bench`.

## Special thanks
Originally based on https://github.com/ejmahler/strength_reduce