diff --git a/docs/performance.md b/docs/performance.md index 0ce1d93..9df9a93 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -11,11 +11,14 @@ smaller percentages are slower. Absolute ns/op values are host-specific appear when the kernel grants perf-counter access. Known slow paths (measured in the tables below, by design): -arithmetic with a rational-raw operand takes the exact rational path -(the mixed integer/notch-offset add and cross-grid non-integer stores -now have folded integer fast paths, gated on imax-safe spans); -`bound` element-wise loops keep a per-element range check — -prefer `bnd::sum`'s bulk check, which validates the total instead. +arithmetic with a rational-raw operand takes the exact rational path — +its cost is measured directly in the exact-backed / rational-engine +tables against a hand-rolled reduced int64 fraction (the mixed +integer/notch-offset add and cross-grid non-integer stores have folded +integer fast paths, gated on imax-safe spans); a rational RHS in a +compound assign takes the same exact branch; `bound` +element-wise loops keep a per-element range check — prefer `bnd::sum`'s +bulk check, which validates the total instead. Warning, results might be unstable: * CPU frequency scaling enabled: CPU 0 between 800.0 and 4,900.0 MHz @@ -27,270 +30,473 @@ Recommendations | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | construct (u8 [0,200]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------- -| 100.0% | 4.65 | 215,140,715.86 | 1.8% | 98.00 | 16.75 | 5.851 | 12.00 | 0.1% | 0.02 | `native uint8` -| 60.4% | 7.70 | 129,875,137.48 | 1.9% | 162.00 | 27.75 | 5.838 | 21.00 | 0.0% | 0.03 | `native clamped` -| 84.7% | 5.49 | 182,222,826.93 | 1.7% | 124.00 | 19.79 | 6.265 | 17.00 | 0.0% | 0.02 | `checked (exceptions)` -| 34.0% | 13.67 | 73,135,681.65 | 0.8% | 211.00 | 49.29 | 4.281 | 30.00 | 0.0% | 0.05 | `bound` +| 100.0% | 4.71 | 212,387,574.16 | 2.4% | 98.00 | 16.96 | 5.777 | 12.00 | 0.1% | 0.02 | `native uint8` +| 63.5% | 7.41 | 134,887,379.03 | 1.9% | 162.00 | 26.73 | 6.060 | 21.00 | 0.0% | 0.03 | `native clamped` +| 87.6% | 5.37 | 186,081,000.44 | 0.6% | 124.00 | 19.34 | 6.412 | 17.00 | 0.0% | 0.02 | `checked (exceptions)` +| 34.3% | 13.71 | 72,934,577.29 | 0.6% | 211.00 | 49.41 | 4.270 | 30.00 | 0.0% | 0.05 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | add (u8 [0,200]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- -| 100.0% | 6.66 | 150,077,819.97 | 1.1% | 151.00 | 24.02 | 6.286 | 17.00 | 0.0% | 0.02 | `native uint8` -| 66.2% | 10.06 | 99,370,947.54 | 0.6% | 216.00 | 36.21 | 5.965 | 26.00 | 0.0% | 0.04 | `native clamped` -| 59.6% | 11.18 | 89,469,719.90 | 2.9% | 234.00 | 40.21 | 5.819 | 31.00 | 0.0% | 0.04 | `checked (exceptions)` -| 16.9% | 39.40 | 25,377,687.42 | 0.5% | 541.00 | 142.03 | 3.809 | 78.00 | 0.0% | 0.14 | `bound` +| 100.0% | 6.52 | 153,342,760.66 | 0.5% | 151.00 | 23.51 | 6.423 | 17.00 | 0.0% | 0.02 | `native uint8` +| 69.2% | 9.42 | 106,119,878.47 | 1.6% | 216.00 | 33.96 | 6.361 | 26.00 | 0.0% | 0.03 | `native clamped` +| 61.7% | 10.57 | 94,630,424.75 | 1.7% | 234.00 | 38.05 | 6.150 | 31.00 | 0.0% | 0.04 | `checked (exceptions)` +| 16.5% | 39.60 | 25,252,861.40 | 0.2% | 541.00 | 142.47 | 3.797 | 78.00 | 0.0% | 0.14 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | sub (u8 [0,200]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- +| 100.0% | 7.08 | 141,144,638.61 | 3.2% | 153.00 | 25.46 | 6.009 | 17.00 | 0.0% | 0.03 | `native uint8` +| 14.5% | 48.92 | 20,441,172.23 | 0.3% | 673.00 | 176.38 | 3.816 | 97.00 | 0.0% | 0.18 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | mul (u8 [0,200]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- -| 100.0% | 6.58 | 152,039,688.44 | 1.9% | 153.00 | 23.72 | 6.451 | 17.00 | 0.0% | 0.02 | `native uint8` -| 54.3% | 12.11 | 82,564,377.86 | 0.8% | 234.00 | 43.68 | 5.358 | 31.00 | 0.0% | 0.04 | `checked (exceptions)` -| 16.8% | 39.09 | 25,583,681.17 | 0.7% | 543.00 | 140.93 | 3.853 | 78.00 | 0.0% | 0.14 | `bound` +| 100.0% | 6.52 | 153,278,337.04 | 1.4% | 153.00 | 23.51 | 6.507 | 17.00 | 0.0% | 0.02 | `native uint8` +| 62.5% | 10.43 | 95,834,460.54 | 3.6% | 234.00 | 37.61 | 6.221 | 31.00 | 0.0% | 0.04 | `checked (exceptions)` +| 16.8% | 38.92 | 25,692,652.19 | 0.1% | 543.00 | 140.31 | 3.870 | 78.00 | 0.0% | 0.14 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | div (u8 [0,200]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- -| 100.0% | 6.59 | 151,774,759.30 | 1.1% | 152.00 | 23.75 | 6.399 | 17.00 | 0.0% | 0.02 | `native uint8` -| 12.7% | 51.77 | 19,315,003.29 | 0.6% | 671.40 | 186.36 | 3.603 | 101.70 | 0.3% | 0.19 | `bound a / b (rational result)` -| 13.2% | 49.73 | 20,106,743.70 | 0.2% | 643.40 | 179.23 | 3.590 | 96.70 | 0.3% | 0.18 | `bound div(a, b, truncated)` +| 100.0% | 6.49 | 154,136,510.54 | 0.8% | 152.00 | 23.39 | 6.499 | 17.00 | 0.0% | 0.02 | `native uint8` +| 12.5% | 51.78 | 19,313,193.43 | 0.4% | 671.40 | 186.26 | 3.605 | 101.70 | 0.3% | 0.19 | `bound a / b (rational result)` +| 13.2% | 49.20 | 20,323,362.61 | 0.1% | 643.40 | 177.40 | 3.627 | 96.70 | 0.3% | 0.18 | `bound div(a, b, truncated)` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | mod (u8 [20,200] % [1,9]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- +| 100.0% | 6.77 | 147,712,204.21 | 1.2% | 154.00 | 24.41 | 6.310 | 17.00 | 0.0% | 0.02 | `native %` +| 11.7% | 57.90 | 17,271,258.98 | 0.6% | 810.40 | 208.54 | 3.886 | 123.70 | 0.3% | 0.21 | `bound % (divisor excludes 0)` +| 13.6% | 49.83 | 20,068,524.16 | 0.3% | 642.40 | 179.65 | 3.576 | 96.70 | 0.3% | 0.18 | `bound mod(a, b, truncated) (zero-checked)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | increment (u32 [0,200000]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------------- -| 100.0% | 2.32 | 430,595,167.34 | 0.8% | 43.00 | 8.37 | 5.135 | 7.00 | 0.0% | 0.01 | `native ++` -| 22.7% | 10.23 | 97,777,934.09 | 1.2% | 135.00 | 36.88 | 3.660 | 23.00 | 0.0% | 0.04 | `bound ++` +| 100.0% | 2.34 | 428,242,150.08 | 1.4% | 43.00 | 8.41 | 5.112 | 7.00 | 0.0% | 0.01 | `native ++` +| 24.3% | 9.61 | 104,053,983.57 | 3.0% | 135.00 | 34.65 | 3.896 | 23.00 | 0.0% | 0.03 | `bound ++` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | compound -= (u8 [0,200]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------- +| 100.0% | 6.79 | 147,190,585.95 | 0.8% | 154.00 | 24.50 | 6.286 | 17.00 | 0.0% | 0.02 | `native -=` +| 24.2% | 28.09 | 35,603,759.12 | 0.4% | 442.00 | 101.24 | 4.366 | 63.00 | 0.0% | 0.10 | `bound -=` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | compound -= (Q8.8) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------- +| 100.0% | 6.46 | 154,793,325.99 | 1.2% | 153.00 | 23.30 | 6.568 | 17.00 | 0.0% | 0.02 | `native int32 -=` +| 21.1% | 30.65 | 32,622,364.03 | 0.5% | 468.00 | 110.52 | 4.235 | 67.00 | 0.0% | 0.11 | `bound -=` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | compound *= (u8 [0,200]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------- +| 100.0% | 6.40 | 156,143,224.27 | 1.4% | 150.00 | 23.10 | 6.494 | 17.00 | 0.0% | 0.02 | `native *=` +| 12.2% | 52.35 | 19,103,294.45 | 0.7% | 699.00 | 188.65 | 3.705 | 101.00 | 0.0% | 0.19 | `bound *=` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | compound += rational (Q8.8) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------- +| 100.0% | 5.05 | 197,970,394.20 | 0.7% | 107.00 | 18.21 | 5.875 | 12.00 | 0.1% | 0.02 | `native int32 += k` +| 3.0% | 170.90 | 5,851,442.75 | 0.5% | 2,302.18 | 616.08 | 3.737 | 420.81 | 0.5% | 0.61 | `bound += rational{k, 256}` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | compound /= (u8 [0,200]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------- -| 100.0% | 6.98 | 143,281,595.03 | 0.8% | 154.00 | 25.16 | 6.120 | 17.00 | 0.0% | 0.03 | `native /=` -| 9.8% | 71.49 | 13,987,824.04 | 0.6% | 929.54 | 257.62 | 3.608 | 144.77 | 0.2% | 0.26 | `bound /=` +| 100.0% | 6.89 | 145,069,980.12 | 1.1% | 154.00 | 24.74 | 6.225 | 17.00 | 0.0% | 0.02 | `native /=` +| 9.7% | 71.18 | 14,048,068.92 | 0.3% | 929.54 | 256.22 | 3.628 | 144.77 | 0.2% | 0.25 | `bound /=` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | accumulate 1000 (u32 [0,200000], per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------------------------- -| 100.0% | 0.83 | 1,201,834,570.47 | 3.1% | 24.11 | 3.00 | 8.036 | 1.02 | 0.1% | 0.01 | `native loop` -| 35.6% | 2.34 | 428,101,739.81 | 2.1% | 59.12 | 8.41 | 7.028 | 7.02 | 0.0% | 0.01 | `bound loop` -| 16.2% | 5.15 | 194,354,199.97 | 1.1% | 101.12 | 18.55 | 5.451 | 15.02 | 0.0% | 0.02 | `bound loop` -| 51.4% | 1.62 | 617,772,937.83 | 1.8% | 34.20 | 5.83 | 5.862 | 5.19 | 0.0% | 0.01 | `bnd::sum (bulk check)` -| 108.7% | 0.77 | 1,306,387,591.74 | 0.6% | 23.12 | 2.76 | 8.375 | 1.02 | 0.1% | 0.01 | `std::accumulate native` -| 3.0% | 27.80 | 35,977,317.79 | 1.0% | 329.24 | 100.18 | 3.287 | 48.04 | 0.0% | 0.10 | `std::accumulate bound` +| 100.0% | 0.83 | 1,207,700,733.85 | 2.2% | 24.11 | 2.99 | 8.076 | 1.02 | 0.1% | 0.01 | `native loop` +| 36.4% | 2.28 | 439,119,068.60 | 0.7% | 59.12 | 8.20 | 7.205 | 7.02 | 0.0% | 0.01 | `bound loop` +| 15.9% | 5.20 | 192,229,257.63 | 0.6% | 101.12 | 18.76 | 5.391 | 15.02 | 0.0% | 0.02 | `bound loop` +| 52.0% | 1.59 | 628,293,078.27 | 0.8% | 34.20 | 5.74 | 5.959 | 5.19 | 0.0% | 0.01 | `bnd::sum (bulk check)` +| 107.8% | 0.77 | 1,301,674,339.53 | 0.8% | 23.12 | 2.77 | 8.347 | 1.02 | 0.1% | 0.01 | `std::accumulate native` +| 2.9% | 28.50 | 35,088,563.53 | 1.9% | 329.24 | 102.72 | 3.205 | 48.04 | 0.0% | 0.10 | `std::accumulate bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | add (signed [-100k,100k]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- -| 100.0% | 6.55 | 152,777,105.84 | 0.8% | 150.00 | 23.60 | 6.356 | 17.00 | 0.0% | 0.02 | `native int` -| 16.7% | 39.17 | 25,527,591.83 | 0.5% | 535.00 | 141.09 | 3.792 | 78.00 | 0.0% | 0.14 | `bound` +| 100.0% | 6.73 | 148,634,069.13 | 1.5% | 150.00 | 24.26 | 6.183 | 17.00 | 0.0% | 0.02 | `native int` +| 17.2% | 39.01 | 25,632,454.91 | 0.2% | 535.00 | 140.67 | 3.803 | 78.00 | 0.0% | 0.14 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | mul (signed [-100k,100k]) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- -| 100.0% | 6.65 | 150,332,269.40 | 1.9% | 150.00 | 23.99 | 6.253 | 17.00 | 0.0% | 0.02 | `native int` -| 17.0% | 39.11 | 25,568,975.04 | 0.6% | 537.00 | 140.99 | 3.809 | 78.00 | 0.0% | 0.14 | `bound` +| 100.0% | 6.55 | 152,596,711.34 | 1.3% | 150.00 | 23.63 | 6.348 | 17.00 | 0.0% | 0.02 | `native int` +| 16.6% | 39.39 | 25,388,708.09 | 0.8% | 537.00 | 141.91 | 3.784 | 78.00 | 0.0% | 0.14 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | sub (signed [-100k,100k]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- +| 100.0% | 7.29 | 137,195,250.88 | 1.0% | 151.00 | 26.28 | 5.745 | 17.00 | 0.0% | 0.03 | `native int` +| 14.8% | 49.21 | 20,321,601.89 | 0.3% | 665.00 | 177.11 | 3.755 | 97.00 | 0.0% | 0.18 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | negate (signed [-100k,100k]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------------- +| 100.0% | 4.50 | 222,258,248.60 | 0.5% | 99.00 | 16.23 | 6.101 | 12.00 | 0.1% | 0.02 | `native int` +| 20.3% | 22.15 | 45,152,032.75 | 0.4% | 306.00 | 79.77 | 3.836 | 46.00 | 0.0% | 0.08 | `bound unary -` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | add (mixed integer + 1/4-notch grids) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------------------- -| 100.0% | 5.08 | 196,924,369.64 | 1.6% | 106.00 | 18.29 | 5.795 | 12.00 | 0.1% | 0.02 | `native int<<2 + q2` -| 16.4% | 30.99 | 32,268,013.24 | 0.7% | 415.00 | 111.60 | 3.719 | 60.00 | 0.0% | 0.11 | `bound` +| 100.0% | 5.14 | 194,376,660.38 | 1.8% | 106.00 | 18.54 | 5.719 | 12.00 | 0.1% | 0.02 | `native int<<2 + q2` +| 16.6% | 30.94 | 32,321,457.69 | 0.3% | 415.00 | 111.49 | 3.722 | 60.00 | 0.0% | 0.11 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | accumulate 1000 (signed, per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------------------- -| 100.0% | 0.81 | 1,239,285,915.29 | 0.8% | 24.11 | 2.91 | 8.288 | 1.02 | 0.1% | 0.01 | `native loop` -| 35.5% | 2.27 | 440,098,073.66 | 0.4% | 59.12 | 8.19 | 7.223 | 7.02 | 0.0% | 0.01 | `bound loop` +| 100.0% | 0.92 | 1,091,056,365.88 | 11.0% | 24.11 | 3.30 | 7.298 | 1.02 | 0.1% | 0.01 | :wavy_dash: `native loop` (Unstable with ~1,231.2 iters. Increase `minEpochIterations` to e.g. 12312) +| 40.2% | 2.28 | 438,590,857.12 | 0.4% | 59.12 | 8.22 | 7.191 | 7.02 | 0.0% | 0.01 | `bound loop` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 construct |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------- -| 100.0% | 4.69 | 213,223,419.25 | 0.4% | 99.00 | 16.89 | 5.861 | 12.00 | 0.1% | 0.02 | `native int<<8` -| 30.8% | 15.21 | 65,764,322.14 | 1.3% | 224.00 | 54.47 | 4.113 | 32.00 | 0.0% | 0.05 | `bound` -| 31.5% | 14.90 | 67,102,449.60 | 0.8% | 232.00 | 53.73 | 4.318 | 34.00 | 0.0% | 0.05 | `bound` +| 100.0% | 4.52 | 221,097,121.40 | 0.5% | 99.00 | 16.30 | 6.073 | 12.00 | 0.1% | 0.02 | `native int<<8` +| 30.6% | 14.77 | 67,703,003.23 | 0.5% | 224.00 | 53.28 | 4.204 | 32.00 | 0.0% | 0.05 | `bound` +| 30.8% | 14.71 | 67,996,260.78 | 1.0% | 232.00 | 53.03 | 4.375 | 34.00 | 0.0% | 0.05 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 add |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------- -| 100.0% | 6.57 | 152,180,696.93 | 1.4% | 152.00 | 23.68 | 6.418 | 17.00 | 0.0% | 0.02 | `native` -| 18.6% | 35.30 | 28,329,291.07 | 0.5% | 529.00 | 127.21 | 4.159 | 76.00 | 0.0% | 0.13 | `bound` -| 19.0% | 34.67 | 28,843,760.37 | 0.5% | 549.00 | 125.00 | 4.392 | 80.00 | 0.0% | 0.12 | `bound` +| 100.0% | 6.58 | 151,860,783.33 | 0.5% | 152.00 | 23.73 | 6.406 | 17.00 | 0.0% | 0.02 | `native` +| 18.7% | 35.14 | 28,460,502.62 | 0.6% | 529.00 | 126.67 | 4.176 | 76.00 | 0.0% | 0.13 | `bound` +| 19.0% | 34.63 | 28,877,103.36 | 0.4% | 549.00 | 124.86 | 4.397 | 80.00 | 0.0% | 0.12 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 sub +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------- +| 100.0% | 6.33 | 158,076,676.32 | 0.7% | 151.00 | 22.81 | 6.620 | 17.00 | 0.0% | 0.02 | `native` +| 15.5% | 40.93 | 24,432,900.14 | 0.7% | 615.00 | 147.42 | 4.172 | 87.00 | 0.0% | 0.15 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 mul |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------- -| 100.0% | 7.06 | 141,598,251.41 | 3.7% | 152.00 | 25.46 | 5.969 | 17.00 | 0.0% | 0.03 | `native (a*b)>>8` -| 19.1% | 36.94 | 27,074,530.42 | 0.4% | 536.00 | 133.16 | 4.025 | 76.00 | 0.0% | 0.13 | `bound` +| 100.0% | 6.47 | 154,482,574.14 | 1.7% | 152.00 | 23.29 | 6.528 | 17.00 | 0.0% | 0.02 | `native (a*b)>>8` +| 17.3% | 37.40 | 26,735,517.83 | 0.4% | 536.00 | 134.34 | 3.990 | 76.00 | 0.0% | 0.13 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 div (truncated) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------- -| 100.0% | 6.89 | 145,197,403.50 | 2.3% | 153.00 | 24.81 | 6.168 | 17.00 | 0.0% | 0.03 | `native (a<<8)/b` -| 15.5% | 44.44 | 22,501,912.32 | 0.6% | 609.75 | 160.14 | 3.808 | 90.38 | 0.4% | 0.16 | `bound div(a, b, truncated)` +| 100.0% | 6.87 | 145,516,842.21 | 1.8% | 153.00 | 24.59 | 6.221 | 17.00 | 0.0% | 0.02 | `native (a<<8)/b` +| 15.4% | 44.70 | 22,370,582.02 | 0.5% | 609.75 | 160.82 | 3.792 | 90.38 | 0.4% | 0.16 | `bound div(a, b, truncated)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 accumulate 1000 (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------------------- -| 100.0% | 0.87 | 1,150,869,420.52 | 8.5% | 24.11 | 3.13 | 7.693 | 1.02 | 0.1% | 0.01 | :wavy_dash: `native loop` (Unstable with ~1,244.6 iters. Increase `minEpochIterations` to e.g. 12446) -| 37.9% | 2.29 | 436,617,183.39 | 0.6% | 62.12 | 8.26 | 7.520 | 7.02 | 0.0% | 0.01 | `bound loop` +| 100.0% | 0.81 | 1,237,386,989.71 | 0.8% | 24.11 | 2.91 | 8.275 | 1.02 | 0.1% | 0.01 | `native loop` +| 35.3% | 2.29 | 437,302,693.77 | 0.2% | 62.12 | 8.24 | 7.535 | 7.02 | 0.0% | 0.01 | `bound loop` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q1.14 signed add |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- -| 100.0% | 3.85 | 259,868,794.81 | 0.2% | 57.00 | 13.88 | 4.107 | 7.00 | 0.1% | 0.01 | `native int` -| 25.4% | 15.13 | 66,072,291.72 | 0.5% | 222.00 | 54.42 | 4.079 | 33.00 | 0.0% | 0.05 | `bound (raw-level)` +| 100.0% | 3.86 | 259,112,787.39 | 0.5% | 57.00 | 13.92 | 4.096 | 7.00 | 0.1% | 0.01 | `native int` +| 25.3% | 15.26 | 65,541,481.18 | 0.5% | 222.00 | 55.01 | 4.036 | 33.00 | 0.0% | 0.05 | `bound (raw-level)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q16.16 add |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 6.47 | 154,496,822.97 | 0.8% | 154.00 | 23.34 | 6.599 | 17.00 | 0.0% | 0.02 | `native int64` -| 18.4% | 35.10 | 28,487,171.03 | 0.6% | 525.00 | 126.47 | 4.151 | 76.00 | 0.0% | 0.13 | `bound` +| 100.0% | 6.41 | 156,077,701.54 | 0.5% | 154.00 | 23.10 | 6.667 | 17.00 | 0.0% | 0.02 | `native int64` +| 18.3% | 35.10 | 28,490,707.58 | 1.0% | 525.00 | 125.29 | 4.190 | 76.00 | 0.0% | 0.13 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | assign from double ([-40,60] step 0.5) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------------------------- -| 100.0% | 72.84 | 13,729,613.30 | 0.9% | 1,387.80 | 262.42 | 5.289 | 221.20 | 0.0% | 0.26 | `truncate (snap)` -| 98.6% | 73.89 | 13,533,092.52 | 0.6% | 1,394.20 | 266.20 | 5.237 | 223.40 | 0.0% | 0.26 | `round_nearest` +| 100.0% | 75.56 | 13,235,007.49 | 0.3% | 1,387.80 | 272.22 | 5.098 | 221.20 | 0.0% | 0.27 | `truncate (snap)` +| 102.3% | 73.88 | 13,535,998.90 | 0.5% | 1,394.20 | 266.36 | 5.234 | 223.40 | 0.0% | 0.26 | `round_nearest` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | Q8.8 store |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 72.25 | 13,841,373.59 | 0.5% | 1,042.39 | 260.43 | 4.003 | 192.48 | 0.3% | 0.26 | `from fraction (rational)` -| 107.4% | 67.27 | 14,865,483.63 | 0.5% | 1,234.34 | 241.76 | 5.106 | 198.48 | 0.0% | 0.24 | `from double` +| 100.0% | 73.84 | 13,542,569.56 | 0.5% | 1,042.39 | 266.24 | 3.915 | 192.48 | 0.3% | 0.26 | `from fraction (rational)` +| 108.5% | 68.06 | 14,693,756.46 | 0.4% | 1,234.34 | 245.37 | 5.031 | 198.48 | 0.0% | 0.24 | `from double` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | cross-grid assign |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------ -| 100.0% | 12.16 | 82,207,791.48 | 0.5% | 176.00 | 43.82 | 4.016 | 25.00 | 0.0% | 0.04 | `same-grid copy` -| 47.8% | 25.46 | 39,282,237.90 | 0.9% | 359.00 | 91.41 | 3.928 | 50.00 | 0.0% | 0.09 | `integer mapping ([0,100] -> [-500,9000])` -| 12.3% | 99.26 | 10,074,974.39 | 0.3% | 1,351.03 | 357.53 | 3.779 | 234.58 | 0.6% | 0.36 | `non-integer snap (1/10 -> 1/4 grid)` +| 100.0% | 12.11 | 82,599,954.28 | 0.4% | 176.00 | 43.66 | 4.032 | 25.00 | 0.0% | 0.04 | `same-grid copy` +| 47.3% | 25.59 | 39,076,962.46 | 0.5% | 359.00 | 92.26 | 3.891 | 50.00 | 0.0% | 0.09 | `integer mapping ([0,100] -> [-500,9000])` +| 12.0% | 100.82 | 9,918,666.17 | 0.2% | 1,351.03 | 363.44 | 3.717 | 234.58 | 0.6% | 0.36 | `non-integer snap (1/10 -> 1/4 grid)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | comparison < |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------- -| 100.0% | 7.02 | 142,405,064.55 | 1.2% | 152.00 | 25.31 | 6.006 | 17.00 | 0.0% | 0.03 | `native int16` -| 74.0% | 9.50 | 105,318,264.38 | 1.9% | 202.00 | 34.23 | 5.901 | 28.00 | 0.0% | 0.03 | `bound same grid` -| 62.6% | 11.21 | 89,188,196.20 | 2.6% | 224.97 | 40.41 | 5.567 | 31.98 | 0.1% | 0.04 | `bound index raw, same notch, cross interval` +| 100.0% | 7.02 | 142,522,360.03 | 0.7% | 152.00 | 25.29 | 6.010 | 17.00 | 0.0% | 0.03 | `native int16` +| 79.2% | 8.86 | 112,833,536.98 | 2.0% | 202.00 | 31.96 | 6.321 | 28.00 | 0.0% | 0.03 | `bound same grid` +| 64.0% | 10.96 | 91,277,549.15 | 2.0% | 224.97 | 39.40 | 5.710 | 31.98 | 0.1% | 0.04 | `bound index raw, same notch, cross interval` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | comparison == +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------- +| 100.0% | 7.01 | 142,560,703.77 | 2.0% | 152.00 | 25.29 | 6.011 | 17.00 | 0.0% | 0.02 | `native int16` +| 76.5% | 9.17 | 109,036,122.78 | 3.0% | 186.00 | 33.07 | 5.625 | 23.00 | 0.0% | 0.03 | `bound same grid` +| 100.2% | 7.00 | 142,864,817.85 | 0.8% | 136.00 | 25.23 | 5.390 | 18.00 | 0.0% | 0.03 | `bound == int scalar (value raw)` +| 100.4% | 6.99 | 143,079,607.35 | 0.5% | 139.00 | 25.20 | 5.516 | 18.00 | 0.0% | 0.02 | `bound Q8.8 == int scalar (index raw)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | casts (s9k value into u200) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------- -| 100.0% | 5.87 | 170,372,597.98 | 1.2% | 127.98 | 21.15 | 6.051 | 16.98 | 0.0% | 0.02 | `native clamp` -| 12.9% | 45.64 | 21,910,893.35 | 0.2% | 727.77 | 164.48 | 4.425 | 110.99 | 0.0% | 0.16 | `clamp_cast` -| 19.7% | 29.87 | 33,478,817.39 | 0.7% | 440.00 | 107.59 | 4.090 | 63.00 | 0.0% | 0.11 | `checked construction (in range)` +| 100.0% | 5.87 | 170,421,808.30 | 0.8% | 127.98 | 21.16 | 6.048 | 16.98 | 0.0% | 0.02 | `native clamp` +| 13.0% | 45.03 | 22,207,769.48 | 0.2% | 727.77 | 162.28 | 4.485 | 110.99 | 0.0% | 0.16 | `clamp_cast` +| 20.1% | 29.21 | 34,239,561.12 | 0.4% | 440.00 | 105.29 | 4.179 | 63.00 | 0.0% | 0.10 | `checked construction (in range)` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | wrap_cast (s9k value into u200) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------------- +| 100.0% | 3.67 | 272,363,412.57 | 0.5% | 85.00 | 13.24 | 6.420 | 7.00 | 0.1% | 0.01 | `native euclidean mod` +| 7.9% | 46.67 | 21,428,013.80 | 0.2% | 698.60 | 168.25 | 4.152 | 100.23 | 0.0% | 0.17 | `wrap_cast` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | sentinel store (50% out of range) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------------- +| 100.0% | 3.44 | 290,718,600.68 | 0.1% | 63.50 | 12.41 | 5.119 | 8.50 | 0.2% | 0.01 | `native branch + flag value` +| 20.9% | 16.48 | 60,669,651.72 | 0.6% | 227.26 | 59.42 | 3.825 | 30.98 | 0.0% | 0.06 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | convert out (bound -> scalar) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------ +| 100.0% | 4.85 | 206,172,153.20 | 0.5% | 99.00 | 17.48 | 5.663 | 12.00 | 0.1% | 0.02 | `native int passthrough` +| 56.4% | 8.61 | 116,193,208.33 | 0.2% | 137.00 | 31.02 | 4.416 | 20.00 | 0.0% | 0.03 | `static_cast (integer grid)` +| 43.3% | 11.19 | 89,358,184.84 | 0.2% | 195.00 | 40.35 | 4.833 | 29.00 | 0.0% | 0.04 | `to (expected)` +| 63.9% | 7.59 | 131,773,818.94 | 0.6% | 126.00 | 27.36 | 4.605 | 18.00 | 0.0% | 0.03 | `static_cast (f64-backed)` +| 12.7% | 38.06 | 26,277,680.92 | 0.6% | 518.52 | 137.22 | 3.779 | 95.15 | 0.3% | 0.14 | `numerator() (Q8.8)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | dot (2D, signed) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------- -| 100.0% | 11.40 | 87,699,830.52 | 3.0% | 255.00 | 41.09 | 6.205 | 27.00 | 0.0% | 0.04 | `native int` -| 11.8% | 96.75 | 10,336,119.74 | 0.1% | 1,226.00 | 348.32 | 3.520 | 176.00 | 0.0% | 0.35 | `bnd::dot` +| 100.0% | 11.01 | 90,827,608.75 | 0.8% | 255.00 | 39.65 | 6.431 | 27.00 | 0.0% | 0.04 | `native int` +| 11.9% | 92.30 | 10,834,732.98 | 0.3% | 1,226.00 | 332.65 | 3.686 | 176.00 | 0.0% | 0.33 | `bnd::dot` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | min / max (same grid) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------- +| 100.0% | 8.23 | 121,556,181.01 | 0.8% | 155.49 | 29.67 | 5.241 | 19.49 | 0.6% | 0.03 | `native std::min` +| 29.0% | 28.33 | 35,302,321.68 | 1.1% | 444.48 | 102.13 | 4.352 | 66.48 | 0.2% | 0.10 | `bnd::min` +| 28.9% | 28.47 | 35,130,417.78 | 1.7% | 444.48 | 102.58 | 4.333 | 66.49 | 0.2% | 0.10 | `bnd::max` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | min (mixed grids -> hull) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- +| 100.0% | 7.85 | 127,313,706.30 | 2.7% | 139.50 | 28.33 | 4.924 | 15.50 | 0.8% | 0.03 | `native int on 1/10 lattice` +| 19.2% | 40.92 | 24,439,975.93 | 0.3% | 636.50 | 147.52 | 4.315 | 93.51 | 0.1% | 0.15 | `bnd::min(narrow, tenths)` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | midpoint (u8 [0,200]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------- +| 100.0% | 6.92 | 144,534,626.95 | 2.7% | 152.00 | 24.95 | 6.092 | 17.00 | 0.0% | 0.02 | `native (a+b)/2` +| 8.2% | 84.83 | 11,788,554.28 | 0.2% | 1,191.04 | 305.74 | 3.896 | 181.96 | 0.4% | 0.30 | `bnd::midpoint` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | lerp (Q8.8, t in [0,1]) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------ +| 100.0% | 6.90 | 144,998,192.42 | 0.6% | 161.00 | 24.87 | 6.475 | 17.00 | 0.0% | 0.02 | `native a + (((b-a)*t)>>8)` +| 9.2% | 75.10 | 13,315,006.76 | 0.4% | 1,112.00 | 270.71 | 4.108 | 157.00 | 0.0% | 0.27 | `bnd::lerp` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | cross (2D, signed) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------- +| 100.0% | 11.30 | 88,470,946.35 | 0.7% | 255.00 | 40.77 | 6.255 | 27.00 | 0.0% | 0.04 | `native int` +| 11.0% | 102.60 | 9,746,780.38 | 0.1% | 1,358.00 | 369.69 | 3.673 | 195.00 | 0.0% | 0.37 | `bnd::cross` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | add_all (4 operands) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------- +| 100.0% | 11.05 | 90,508,827.54 | 0.7% | 250.00 | 39.83 | 6.276 | 27.00 | 0.0% | 0.04 | `native a+b+c+d` +| 11.9% | 92.66 | 10,791,793.85 | 0.3% | 1,225.00 | 333.88 | 3.669 | 176.00 | 0.0% | 0.33 | `bnd::add_all` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | mul_all (3 operands) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------- +| 100.0% | 8.57 | 116,749,457.77 | 0.8% | 199.00 | 30.85 | 6.450 | 22.00 | 0.0% | 0.03 | `native a*b*c` +| 7.2% | 118.52 | 8,437,577.91 | 0.2% | 1,846.00 | 427.28 | 4.320 | 286.00 | 0.0% | 0.43 | `bnd::mul_all` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | f64-backed add (dyadic 1/16384 grid) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------- +| 100.0% | 7.13 | 140,167,155.83 | 0.9% | 147.00 | 25.73 | 5.714 | 17.00 | 0.0% | 0.03 | `native double` +| 42.5% | 16.78 | 59,603,079.25 | 0.5% | 294.00 | 60.48 | 4.861 | 40.00 | 0.0% | 0.06 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | f64-backed mul (dyadic 1/16384 grid) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------- +| 100.0% | 7.27 | 137,643,631.62 | 1.0% | 147.00 | 26.19 | 5.614 | 17.00 | 0.0% | 0.03 | `native double` +| 43.6% | 16.65 | 60,060,324.24 | 0.7% | 289.00 | 60.02 | 4.815 | 39.00 | 0.0% | 0.06 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | exact-backed add (rational storage) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------ +| 100.0% | 36.37 | 27,494,233.70 | 0.4% | 453.96 | 131.12 | 3.462 | 83.59 | 0.9% | 0.04 | `native fraction` +| 505.5% | 7.19 | 138,991,909.56 | 1.1% | 147.00 | 25.94 | 5.667 | 17.00 | 0.0% | 0.01 | `std double (reference)` +| 64.8% | 56.12 | 17,818,606.63 | 0.5% | 800.93 | 202.33 | 3.959 | 131.73 | 0.7% | 0.07 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | exact-backed mul (rational storage) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------ +| 100.0% | 55.13 | 18,140,411.79 | 1.2% | 600.88 | 198.39 | 3.029 | 126.13 | 0.8% | 0.07 | `native fraction` +| 65.0% | 84.86 | 11,784,011.42 | 0.3% | 1,031.64 | 305.84 | 3.373 | 187.39 | 1.0% | 0.10 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | exact-backed div (rational storage) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------ +| 100.0% | 60.08 | 16,643,446.72 | 0.5% | 530.79 | 216.51 | 2.452 | 100.93 | 3.1% | 0.07 | `native fraction` +| 60.2% | 99.81 | 10,019,465.42 | 1.3% | 1,105.73 | 359.58 | 3.075 | 184.03 | 1.9% | 0.12 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | exact-backed compare < +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------- +| 100.0% | 7.44 | 134,398,198.44 | 2.1% | 156.00 | 26.83 | 5.814 | 17.00 | 0.0% | 0.01 | `native fraction cross-multiply` +| 47.9% | 15.52 | 64,415,570.69 | 3.0% | 334.07 | 55.96 | 5.969 | 47.16 | 0.0% | 0.02 | `bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | mixed exact + integer-grid add +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------- +| 100.0% | 45.51 | 21,973,332.72 | 0.8% | 544.47 | 163.93 | 3.321 | 105.05 | 0.8% | 0.05 | `native fraction + int` +| 58.0% | 78.49 | 12,739,828.05 | 0.3% | 1,083.39 | 283.00 | 3.828 | 191.46 | 0.5% | 0.09 | `bound + bound integer grid` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | rational engine add +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------- +| 100.0% | 36.75 | 27,208,828.95 | 1.0% | 453.96 | 132.50 | 3.426 | 83.60 | 0.9% | 0.04 | `native fraction` +| 67.3% | 54.65 | 18,299,484.99 | 1.0% | 729.22 | 196.94 | 3.703 | 130.44 | 0.6% | 0.07 | `detail::rational` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | rational engine mul +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------- +| 100.0% | 54.37 | 18,391,207.91 | 0.5% | 600.88 | 196.02 | 3.065 | 126.13 | 0.8% | 0.07 | `native fraction` +| 65.9% | 82.48 | 12,124,523.95 | 0.8% | 934.65 | 296.97 | 3.147 | 186.39 | 1.0% | 0.10 | `detail::rational` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | rational engine compare < +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------- +| 100.0% | 7.36 | 135,801,013.49 | 1.2% | 156.00 | 26.55 | 5.875 | 17.00 | 0.0% | 0.01 | `native fraction cross-multiply` +| 51.2% | 14.37 | 69,583,478.12 | 3.5% | 300.08 | 51.81 | 5.792 | 41.16 | 0.0% | 0.02 | `detail::rational` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | iterate bound_range 1000 (per element) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------------------------- +| 100.0% | 2.09 | 479,435,125.50 | 0.5% | 53.09 | 7.52 | 7.061 | 6.01 | 0.0% | 0.01 | `native counting loop` +| 34.2% | 6.11 | 163,744,893.03 | 1.4% | 146.20 | 22.02 | 6.639 | 19.03 | 0.0% | 0.02 | `bound_range integer grid` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | iterate bound_range Q-grid 1/256 (per element) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------------------------------- +| 100.0% | 2.08 | 479,975,673.33 | 1.1% | 53.08 | 7.51 | 7.065 | 6.01 | 0.0% | 0.01 | `native counting loop` +| 34.6% | 6.03 | 165,878,577.31 | 0.3% | 145.20 | 21.74 | 6.679 | 19.02 | 0.0% | 0.02 | `bound_range 1/256 grid` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: abs |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 5.11 | 195,660,892.22 | 1.8% | 107.00 | 18.41 | 5.812 | 14.00 | 0.1% | 0.01 | `std double` -| 9.5% | 53.91 | 18,551,050.80 | 0.9% | 1,031.45 | 192.87 | 5.348 | 169.49 | 0.0% | 0.03 | `bnd::math bound` +| 100.0% | 5.06 | 197,544,659.05 | 0.6% | 107.00 | 18.25 | 5.862 | 14.00 | 0.1% | 0.01 | `std double` +| 9.3% | 54.27 | 18,426,855.10 | 1.2% | 1,031.43 | 195.68 | 5.271 | 169.49 | 0.0% | 0.03 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: floor |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ -| 100.0% | 4.79 | 208,585,795.65 | 0.3% | 103.00 | 17.28 | 5.962 | 15.00 | 0.1% | 0.01 | `std double` -| 7.7% | 62.20 | 16,076,152.09 | 0.6% | 1,202.44 | 224.29 | 5.361 | 200.99 | 0.0% | 0.04 | `bnd::math bound` +| 100.0% | 4.93 | 202,786,588.83 | 2.1% | 103.00 | 17.78 | 5.792 | 15.00 | 0.1% | 0.01 | `std double` +| 7.4% | 66.19 | 15,107,037.20 | 0.8% | 1,202.43 | 238.12 | 5.050 | 200.99 | 0.0% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: round |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ -| 100.0% | 5.07 | 197,181,265.96 | 0.5% | 119.25 | 18.29 | 6.520 | 18.25 | 0.1% | 0.01 | `std double` -| 8.2% | 62.17 | 16,085,551.52 | 0.7% | 1,203.81 | 224.22 | 5.369 | 200.74 | 0.0% | 0.04 | `bnd::math bound` +| 100.0% | 5.02 | 199,025,430.74 | 0.2% | 119.25 | 18.12 | 6.580 | 18.25 | 0.1% | 0.01 | `std double` +| 7.4% | 67.93 | 14,720,225.83 | 0.6% | 1,203.80 | 244.92 | 4.915 | 200.74 | 0.0% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: sqrt |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 5.01 | 199,703,431.08 | 0.7% | 108.00 | 18.05 | 5.983 | 17.00 | 0.1% | 0.01 | `std double` -| 10.8% | 46.25 | 21,619,385.63 | 0.8% | 670.00 | 166.73 | 4.018 | 122.00 | 0.3% | 0.03 | `bnd::math bound` +| 100.0% | 4.97 | 201,190,952.13 | 0.3% | 108.00 | 17.93 | 6.025 | 17.00 | 0.1% | 0.01 | `std double` +| 10.5% | 47.15 | 21,207,486.78 | 1.0% | 670.00 | 169.21 | 3.960 | 122.00 | 0.3% | 0.03 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: exp2 |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 6.15 | 162,729,891.46 | 1.4% | 141.99 | 22.15 | 6.410 | 17.00 | 0.0% | 0.01 | `std double` -| 8.3% | 74.26 | 13,466,129.66 | 0.9% | 974.04 | 267.66 | 3.639 | 179.01 | 0.2% | 0.04 | `bnd::math bound` +| 100.0% | 5.70 | 175,439,302.15 | 0.7% | 141.99 | 20.56 | 6.908 | 17.00 | 0.0% | 0.01 | `std double` +| 7.7% | 74.05 | 13,503,928.36 | 0.5% | 973.96 | 266.91 | 3.649 | 178.99 | 0.2% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: log2 |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 6.32 | 158,144,640.23 | 1.2% | 153.72 | 22.79 | 6.746 | 17.00 | 0.0% | 0.01 | `std double` -| 9.3% | 67.91 | 14,725,203.77 | 0.4% | 926.56 | 244.82 | 3.785 | 164.02 | 0.2% | 0.04 | `bnd::math bound` +| 100.0% | 6.28 | 159,189,168.35 | 0.9% | 153.72 | 22.66 | 6.785 | 17.00 | 0.0% | 0.01 | `std double` +| 9.2% | 67.93 | 14,720,648.51 | 0.5% | 926.56 | 244.97 | 3.782 | 164.02 | 0.2% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: exp |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 7.85 | 127,325,054.45 | 6.1% | 157.99 | 28.08 | 5.626 | 23.00 | 0.0% | 0.01 | :wavy_dash: `std double` (Unstable with ~137,790.5 iters. Increase `minEpochIterations` to e.g. 1377905) -| 10.6% | 73.80 | 13,550,877.67 | 1.2% | 965.35 | 266.01 | 3.629 | 177.62 | 0.2% | 0.04 | `bnd::math bound` +| 100.0% | 6.69 | 149,526,149.23 | 1.6% | 157.99 | 24.10 | 6.555 | 23.00 | 0.0% | 0.01 | `std double` +| 9.0% | 74.70 | 13,387,122.92 | 0.4% | 965.35 | 269.34 | 3.584 | 177.62 | 0.2% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: log |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 6.69 | 149,557,726.74 | 0.8% | 154.00 | 24.11 | 6.388 | 20.00 | 0.0% | 0.01 | `std double` -| 10.2% | 65.29 | 15,315,628.59 | 0.8% | 914.50 | 235.36 | 3.885 | 162.00 | 0.2% | 0.04 | `bnd::math bound` +| 100.0% | 6.53 | 153,246,070.55 | 1.6% | 154.00 | 23.53 | 6.544 | 20.00 | 0.0% | 0.01 | `std double` +| 10.1% | 64.65 | 15,468,488.09 | 0.5% | 914.49 | 232.94 | 3.926 | 162.00 | 0.2% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: pow10 |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ -| 100.0% | 11.54 | 86,655,735.47 | 2.2% | 231.98 | 41.65 | 5.570 | 25.00 | 0.0% | 0.01 | `std double` -| 10.7% | 107.49 | 9,302,939.91 | 0.7% | 1,241.79 | 387.37 | 3.206 | 223.40 | 0.2% | 0.06 | `bnd::math bound` +| 100.0% | 11.19 | 89,328,172.98 | 0.5% | 231.98 | 40.30 | 5.756 | 25.00 | 0.0% | 0.01 | `std double` +| 10.5% | 106.89 | 9,355,083.87 | 0.3% | 1,241.77 | 385.14 | 3.224 | 223.40 | 0.2% | 0.06 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: sin |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 7.53 | 132,782,500.53 | 0.8% | 174.53 | 27.11 | 6.437 | 21.64 | 0.0% | 0.01 | `std double` -| 12.0% | 62.89 | 15,900,369.93 | 1.0% | 845.10 | 226.63 | 3.729 | 156.51 | 0.3% | 0.04 | `bnd::math bound` +| 100.0% | 7.62 | 131,218,273.40 | 1.6% | 174.52 | 27.47 | 6.353 | 21.64 | 0.0% | 0.01 | `std double` +| 11.8% | 64.41 | 15,524,824.59 | 0.6% | 845.13 | 231.96 | 3.643 | 156.52 | 0.3% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: cos |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 7.56 | 132,315,903.27 | 0.9% | 176.22 | 27.24 | 6.469 | 22.31 | 0.0% | 0.01 | `std double` -| 11.8% | 63.94 | 15,639,613.26 | 0.6% | 855.21 | 230.46 | 3.711 | 157.97 | 0.2% | 0.04 | `bnd::math bound` +| 100.0% | 7.43 | 134,665,855.31 | 0.3% | 176.21 | 26.77 | 6.582 | 22.31 | 0.0% | 0.01 | `std double` +| 11.7% | 63.62 | 15,717,966.29 | 0.3% | 855.18 | 229.44 | 3.727 | 157.96 | 0.2% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: tan |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- -| 100.0% | 8.01 | 124,794,069.91 | 1.1% | 172.07 | 28.89 | 5.955 | 22.91 | 0.0% | 0.01 | `std double` -| 8.8% | 90.63 | 11,033,499.46 | 1.2% | 1,177.00 | 326.73 | 3.602 | 222.00 | 0.2% | 0.05 | `bnd::math bound` +| 100.0% | 7.99 | 125,192,361.24 | 0.4% | 172.07 | 28.80 | 5.974 | 22.91 | 0.0% | 0.01 | `std double` +| 8.7% | 91.89 | 10,882,986.57 | 0.5% | 1,177.00 | 331.19 | 3.554 | 222.00 | 0.2% | 0.05 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: atan2 |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ -| 100.0% | 14.04 | 71,200,068.11 | 0.4% | 303.47 | 50.61 | 5.996 | 50.99 | 0.0% | 0.01 | `std double` -| 20.1% | 69.89 | 14,307,400.65 | 0.9% | 994.02 | 251.96 | 3.945 | 176.00 | 0.0% | 0.04 | `bnd::math bound` +| 100.0% | 14.12 | 70,809,918.29 | 0.5% | 303.48 | 50.93 | 5.958 | 50.99 | 0.0% | 0.01 | `std double` +| 20.3% | 69.61 | 14,366,346.84 | 0.5% | 994.02 | 250.89 | 3.962 | 176.00 | 0.0% | 0.04 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: fmod |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 10.31 | 96,961,159.75 | 1.8% | 200.70 | 37.15 | 5.403 | 24.52 | 0.5% | 0.01 | `std double` -| 62.1% | 16.62 | 60,183,278.02 | 1.8% | 285.00 | 59.90 | 4.758 | 39.00 | 0.0% | 0.01 | `bnd::math bound` +| 100.0% | 10.43 | 95,874,157.92 | 1.0% | 200.70 | 37.61 | 5.336 | 24.52 | 0.5% | 0.01 | `std double` +| 62.6% | 16.67 | 59,973,156.87 | 0.8% | 285.00 | 60.12 | 4.740 | 39.00 | 0.0% | 0.01 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: asin |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 7.92 | 126,281,470.46 | 0.2% | 166.55 | 28.51 | 5.843 | 24.30 | 0.0% | 0.01 | `std double` -| 10.1% | 78.24 | 12,780,625.42 | 1.4% | 914.34 | 281.07 | 3.253 | 169.12 | 0.2% | 0.05 | `bnd::math bound` +| 100.0% | 7.66 | 130,472,253.78 | 1.0% | 166.56 | 27.63 | 6.028 | 24.30 | 0.0% | 0.01 | `std double` +| 10.0% | 76.72 | 13,034,872.16 | 0.4% | 914.33 | 276.63 | 3.305 | 169.13 | 0.2% | 0.05 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: tanh |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 11.49 | 87,063,474.38 | 0.6% | 213.07 | 41.40 | 5.147 | 34.63 | 0.0% | 0.01 | `std double` -| 14.5% | 79.30 | 12,610,260.97 | 0.7% | 997.17 | 285.81 | 3.489 | 182.31 | 0.2% | 0.05 | `bnd::math bound` +| 100.0% | 11.45 | 87,310,875.34 | 1.1% | 213.07 | 41.24 | 5.167 | 34.63 | 0.0% | 0.01 | `std double` +| 14.5% | 78.79 | 12,691,281.00 | 0.4% | 997.17 | 283.70 | 3.515 | 182.31 | 0.2% | 0.05 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: cbrt |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- -| 100.0% | 12.59 | 79,455,119.98 | 1.3% | 240.72 | 45.38 | 5.305 | 40.24 | 0.0% | 0.01 | `std double` -| 11.3% | 110.94 | 9,013,855.84 | 0.2% | 1,226.71 | 399.92 | 3.067 | 226.38 | 0.3% | 0.07 | `bnd::math bound` +| 100.0% | 12.57 | 79,546,672.91 | 1.6% | 240.72 | 45.33 | 5.310 | 40.24 | 0.0% | 0.01 | `std double` +| 11.4% | 110.56 | 9,045,051.42 | 0.5% | 1,226.79 | 398.67 | 3.077 | 226.39 | 0.3% | 0.07 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: hypot |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ -| 100.0% | 9.09 | 110,039,752.95 | 0.7% | 204.85 | 32.76 | 6.252 | 28.61 | 0.0% | 0.01 | `std double` -| 17.5% | 51.98 | 19,237,976.00 | 1.4% | 779.00 | 187.29 | 4.159 | 135.00 | 0.3% | 0.03 | `bnd::math bound` +| 100.0% | 8.99 | 111,218,804.44 | 1.1% | 204.85 | 32.41 | 6.320 | 28.61 | 0.0% | 0.01 | `std double` +| 17.0% | 52.80 | 18,939,971.66 | 0.4% | 779.00 | 190.33 | 4.093 | 135.00 | 0.3% | 0.03 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: ceil +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- +| 100.0% | 4.82 | 207,667,115.81 | 0.4% | 103.00 | 17.36 | 5.932 | 15.00 | 0.1% | 0.01 | `std double` +| 7.3% | 66.33 | 15,076,573.61 | 0.4% | 1,201.19 | 239.03 | 5.025 | 200.49 | 0.0% | 0.04 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: atan +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- +| 100.0% | 7.39 | 135,383,130.06 | 0.4% | 161.38 | 26.64 | 6.059 | 23.06 | 0.0% | 0.01 | `std double` +| 11.3% | 65.29 | 15,315,464.40 | 0.4% | 871.11 | 235.36 | 3.701 | 160.20 | 0.2% | 0.04 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: acos +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- +| 100.0% | 7.86 | 127,196,556.04 | 0.6% | 171.68 | 28.35 | 6.056 | 24.74 | 0.0% | 0.01 | `std double` +| 10.0% | 78.91 | 12,672,074.05 | 0.7% | 921.32 | 284.44 | 3.239 | 169.12 | 0.2% | 0.05 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: sinh +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------- +| 100.0% | 11.93 | 83,811,750.26 | 0.7% | 223.76 | 43.04 | 5.199 | 35.44 | 0.0% | 0.01 | `std double` +| 15.2% | 78.41 | 12,752,919.08 | 0.2% | 991.35 | 282.69 | 3.507 | 181.62 | 0.2% | 0.05 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: log10 +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------ +| 100.0% | 8.31 | 120,327,743.95 | 0.6% | 193.80 | 29.96 | 6.469 | 24.00 | 0.0% | 0.01 | `std double` +| 13.0% | 64.16 | 15,587,180.69 | 0.6% | 926.49 | 231.11 | 4.009 | 164.00 | 0.1% | 0.04 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: pown<3> +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------- +| 100.0% | 8.80 | 113,639,039.15 | 0.6% | 197.00 | 31.73 | 6.208 | 22.00 | 0.0% | 0.01 | `std double` +| 30.2% | 29.13 | 34,325,693.21 | 0.6% | 408.00 | 104.92 | 3.889 | 61.00 | 0.0% | 0.02 | `bnd::math bound` + +| relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: pow (expected) +|---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:--------------------- +| 100.0% | 13.50 | 74,046,928.33 | 1.5% | 280.98 | 49.68 | 5.656 | 30.00 | 0.0% | 0.01 | `std double` +| 11.5% | 117.82 | 8,487,319.44 | 0.4% | 1,445.94 | 424.82 | 3.404 | 257.64 | 0.2% | 0.07 | `bnd::math bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | math: sin (binary32 / float engine) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------------ -| 100.0% | 7.02 | 142,471,819.42 | 1.2% | 142.35 | 25.30 | 5.628 | 19.62 | 0.0% | 0.01 | `std sinf` -| 9.5% | 73.65 | 13,578,265.92 | 1.5% | 941.61 | 265.31 | 3.549 | 165.69 | 0.2% | 0.04 | `bnd::math::flt::sin (f32 bound)` +| 100.0% | 6.74 | 148,339,717.40 | 0.4% | 142.38 | 24.31 | 5.857 | 19.63 | 0.0% | 0.01 | `std sinf` +| 9.3% | 72.64 | 13,766,214.01 | 0.4% | 941.61 | 261.94 | 3.595 | 165.69 | 0.2% | 0.04 | `bnd::math::flt::sin (f32 bound)` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | sort 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------------------- -| 100.0% | 284.17 | 3,518,990.34 | 0.3% | 3,942.63 | 1,024.06 | 3.850 | 439.06 | 0.6% | 1.02 | `native int16` -| 78.7% | 360.88 | 2,771,026.17 | 1.0% | 4,892.36 | 1,300.45 | 3.762 | 642.49 | 0.4% | 1.30 | `bound` +| 100.0% | 281.65 | 3,550,460.81 | 0.5% | 3,942.63 | 1,015.10 | 3.884 | 439.06 | 0.6% | 1.01 | `native int16` +| 80.3% | 350.93 | 2,849,605.17 | 0.4% | 4,892.36 | 1,264.52 | 3.869 | 642.49 | 0.4% | 1.26 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | nth_element 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------ -| 100.0% | 4.87 | 205,338,223.43 | 1.0% | 117.79 | 17.56 | 6.708 | 15.90 | 0.1% | 0.02 | `native int16` -| 42.4% | 11.50 | 86,980,488.65 | 2.5% | 249.37 | 41.45 | 6.016 | 44.09 | 0.1% | 0.04 | `bound` +| 100.0% | 4.90 | 203,999,148.30 | 0.7% | 117.79 | 17.68 | 6.662 | 15.90 | 0.1% | 0.02 | `native int16` +| 44.2% | 11.10 | 90,095,006.47 | 1.3% | 249.37 | 39.99 | 6.236 | 44.09 | 0.1% | 0.04 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | partition 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------- -| 100.0% | 1.38 | 722,807,231.12 | 0.6% | 37.62 | 4.99 | 7.541 | 5.21 | 0.0% | 0.01 | `native int16` -| 25.8% | 5.36 | 186,411,640.01 | 1.0% | 94.62 | 19.33 | 4.895 | 17.21 | 0.1% | 0.02 | `bound` +| 100.0% | 1.39 | 720,297,816.79 | 0.6% | 37.62 | 4.98 | 7.551 | 5.21 | 0.0% | 0.01 | `native int16` +| 25.8% | 5.37 | 186,162,345.98 | 0.9% | 94.62 | 19.36 | 4.888 | 17.21 | 0.1% | 0.02 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | find / count 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:------------------------------- -| 100.0% | 2.52 | 396,320,559.92 | 1.0% | 58.94 | 9.11 | 6.472 | 7.86 | 0.0% | 0.01 | `find native` -| 63.2% | 3.99 | 250,364,280.03 | 0.8% | 88.02 | 14.39 | 6.117 | 12.58 | 0.0% | 0.02 | `find bound` -| 75.5% | 3.34 | 299,294,226.80 | 0.4% | 73.02 | 12.04 | 6.062 | 8.00 | 0.0% | 0.01 | `count native` -| 49.4% | 5.11 | 195,636,650.57 | 2.5% | 107.03 | 18.35 | 5.833 | 14.00 | 0.0% | 0.02 | `count bound` +| 100.0% | 2.54 | 393,398,940.62 | 1.1% | 58.94 | 9.16 | 6.434 | 7.86 | 0.0% | 0.01 | `find native` +| 63.4% | 4.01 | 249,605,116.90 | 0.5% | 88.02 | 14.45 | 6.092 | 12.58 | 0.0% | 0.01 | `find bound` +| 75.1% | 3.39 | 295,279,768.35 | 0.8% | 73.02 | 12.21 | 5.981 | 8.00 | 0.0% | 0.01 | `count native` +| 50.9% | 4.99 | 200,273,017.35 | 1.0% | 107.03 | 17.99 | 5.948 | 14.00 | 0.0% | 0.02 | `count bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | min/max_element 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------------------------- -| 100.0% | 10.96 | 91,260,698.84 | 5.5% | 185.00 | 39.50 | 4.684 | 20.00 | 0.0% | 0.04 | :wavy_dash: `min_element native` (Unstable with ~32.7 iters. Increase `minEpochIterations` to e.g. 327) -| 76.8% | 14.28 | 70,051,383.82 | 3.1% | 241.00 | 51.45 | 4.684 | 32.00 | 0.0% | 0.05 | `min_element bound` -| 106.7% | 10.27 | 97,384,014.07 | 2.5% | 185.27 | 37.02 | 5.004 | 20.00 | 0.0% | 0.04 | `max_element native` -| 77.4% | 14.15 | 70,660,907.96 | 0.7% | 241.27 | 51.01 | 4.730 | 32.00 | 0.0% | 0.05 | `max_element bound` +| 100.0% | 10.74 | 93,106,522.40 | 2.3% | 185.00 | 38.72 | 4.778 | 20.00 | 0.0% | 0.04 | `min_element native` +| 76.1% | 14.11 | 70,862,478.30 | 0.4% | 241.00 | 50.88 | 4.736 | 32.00 | 0.0% | 0.05 | `min_element bound` +| 103.6% | 10.37 | 96,471,597.98 | 1.6% | 185.27 | 37.38 | 4.956 | 20.00 | 0.0% | 0.04 | `max_element native` +| 76.8% | 13.99 | 71,475,278.33 | 0.8% | 241.27 | 50.45 | 4.782 | 32.00 | 0.0% | 0.05 | `max_element bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | lower_bound 10k |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------------- -| 100.0% | 149.55 | 6,686,831.18 | 1.1% | 2,332.12 | 538.61 | 4.330 | 246.29 | 1.1% | 0.54 | `native int16` -| 69.7% | 214.61 | 4,659,515.42 | 0.7% | 3,190.29 | 773.38 | 4.125 | 422.57 | 0.8% | 0.77 | `bound` +| 100.0% | 146.12 | 6,843,753.85 | 0.7% | 2,332.12 | 526.81 | 4.427 | 246.29 | 1.1% | 0.53 | `native int16` +| 69.1% | 211.55 | 4,726,967.36 | 0.4% | 3,190.29 | 761.85 | 4.188 | 422.57 | 0.7% | 0.76 | `bound` | relative | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | transform v+1 10k (per element) |---------:|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:-------------------------------- -| 100.0% | 1.34 | 747,849,447.22 | 0.6% | 38.02 | 4.82 | 7.888 | 3.00 | 0.0% | 0.01 | `native uint8` -| 22.3% | 5.99 | 166,972,693.50 | 1.8% | 102.02 | 21.58 | 4.727 | 14.00 | 0.1% | 0.02 | `bound u255 (16-bit storage)` -| 22.9% | 5.84 | 171,098,796.47 | 1.0% | 102.02 | 20.94 | 4.872 | 14.00 | 0.1% | 0.02 | `bound u254 (8-bit storage)` +| 100.0% | 1.33 | 750,148,485.27 | 0.3% | 38.02 | 4.81 | 7.909 | 3.00 | 0.0% | 0.01 | `native uint8` +| 22.5% | 5.94 | 168,479,084.95 | 1.1% | 102.02 | 21.40 | 4.768 | 14.00 | 0.1% | 0.02 | `bound u255 (16-bit storage)` +| 23.1% | 5.78 | 173,140,685.46 | 1.4% | 102.02 | 20.82 | 4.901 | 14.00 | 0.1% | 0.02 | `bound u254 (8-bit storage)` diff --git a/include/bound/core.hpp b/include/bound/core.hpp index 551ec78..2de9ae0 100644 --- a/include/bound/core.hpp +++ b/include/bound/core.hpp @@ -598,8 +598,8 @@ namespace bnd private: // Out-of-range tail for raw-space compound arithmetic: dispatch on policy - // (clamp/wrap/sentinel/checked) and store back to `Raw`. Called from - // `operator+=(boundable)` only. + // (clamp/wrap/sentinel/checked) and store back to `Raw`. Called from the + // raw fast paths of `operator+=(boundable)` and `operator-=(boundable)`. constexpr bound& apply_raw_overflow(imax new_raw) { if constexpr (P & clamp) @@ -658,7 +658,45 @@ namespace bnd template constexpr bound& operator-=(R const& rhs) - { return *this += (-rhs); } + { + // Raw-space fast path, the subtraction mirror of +='s: with equal + // notches, raw(v_l − v_r) = raw_l − raw_r − bias, where the bias is + // Lower/Notch for an index-raw rhs (its raw is Lower-relative) and 0 + // for a value-raw rhs. Delegating to `+= (-rhs)` instead shifts R's + // Lower by negation and defeats +='s raw path for index-backed grids. + if constexpr (!detail::rational_raw && !detail::rational_raw + && !detail::fp_raw && !detail::fp_raw + && Notch != 0 && Notch == Notch + && (!detail::index_raw + || ((Lower / Notch).has_value() + && detail::abs_den((*(Lower / Notch)).Denominator) == 1))) + { + constexpr imax bias = [] { + if constexpr (detail::index_raw) + { + constexpr auto quotient = *(Lower / Notch); + return (quotient.Denominator < 0) + ? -static_cast(quotient.Numerator) + : static_cast(quotient.Numerator); + } + else + return imax{0}; + }(); + if constexpr (P & (clamp | wrap | checked | sentinel)) + { + imax new_raw = detail::raw_imax(*this) - detail::raw_imax(rhs) - bias; + if (new_raw < detail::RawLo || new_raw > detail::RawHi) + return apply_raw_overflow(new_raw); + Raw = detail::raw_cast(new_raw); + } + else + Raw = detail::raw_cast( + detail::raw_imax(*this) - detail::raw_imax(rhs) - bias); + return *this; + } + else + return *this += (-rhs); + } template A> constexpr bound& operator-=(A const& rhs) @@ -830,11 +868,53 @@ namespace bnd return detail::as_rational(lhs) == detail::as_rational(rhs); } + namespace detail + { + // bound ⋈ integral scalar without the rational decode: with the positive + // notch n/d, value ⋈ c ⟺ (bias + raw)·n ⋈ c·d — both sides exact + // integers (bias + raw is the signed value index, exact ordering AND + // equality since c·d is exact too). Eligible when both cross terms + // provably fit imax for every representable c of type A. + template + inline constexpr bool scalar_index_cmp_fits = []{ + if constexpr (!std::integral || !index_raw || !index_cmp_fits) + return false; + else + { + constexpr umax cap = static_cast(std::numeric_limits::max()); + constexpr umax notch_num = Notch.Numerator; + constexpr umax notch_den = static_cast(Notch.Denominator); // Notch > 0 + constexpr umax index_mag = []{ + constexpr auto lo = *(Lower / Notch); + constexpr auto hi = *(Upper / Notch); + return lo.Numerator > hi.Numerator ? lo.Numerator : hi.Numerator; + }(); + constexpr umax scalar_mag = []{ + umax mag = static_cast(std::numeric_limits::max()); + if constexpr (std::signed_integral) + { + umax min_mag = static_cast( + -(std::numeric_limits::min() + 1)) + 1; + if (min_mag > mag) mag = min_mag; + } + return mag; + }(); + umax product; + return !mul_overflow(index_mag, notch_num, &product) && product <= cap + && !mul_overflow(scalar_mag, notch_den, &product) && product <= cap; + } + }(); + } + template constexpr auto operator<=>(B const& lhs, A rhs) { if constexpr (detail::value_raw) return detail::raw_imax(lhs) <=> static_cast(rhs); + else if constexpr (detail::scalar_index_cmp_fits) + return (detail::index_cmp_bias + detail::raw_imax(lhs)) + * static_cast(Notch.Numerator) + <=> static_cast(rhs) * Notch.Denominator; else return detail::as_rational(lhs) <=> bnd::detail::rational{rhs}; } @@ -844,6 +924,10 @@ namespace bnd { if constexpr (detail::value_raw) return detail::raw_imax(lhs) == static_cast(rhs); + else if constexpr (detail::scalar_index_cmp_fits) + return (detail::index_cmp_bias + detail::raw_imax(lhs)) + * static_cast(Notch.Numerator) + == static_cast(rhs) * Notch.Denominator; else return detail::as_rational(lhs) == bnd::detail::rational{rhs}; } diff --git a/include/bound/detail/assignment.hpp b/include/bound/detail/assignment.hpp index 146d4e3..d059ba4 100644 --- a/include/bound/detail/assignment.hpp +++ b/include/bound/detail/assignment.hpp @@ -709,12 +709,18 @@ namespace bnd::detail } template - static constexpr void store(L& lhs, R const& rhs, P&&) + static constexpr void store(L& lhs, R const& rhs, P&& policy) { if constexpr (fp_raw) // real target: raw IS the value — decode the source and snap to the dyadic // grid (the offset machinery below mis-encodes a double raw). lhs = L::from_raw(Grid.snap_double(as_double(rhs))); + else if constexpr (rational_raw) + // rational target: raw IS the value — snap the decoded source through + // the rational-rhs store (the offset machinery below would round the + // VALUE to a notch index and store that number as the raw). + assignment::store_checked(lhs, as_rational(rhs), policy, + no_action{}); else if constexpr (is_integer_mapping) { // exact: Factor and Offset have integer denominators, no rounding ambiguity diff --git a/include/bound/range.hpp b/include/bound/range.hpp index 901ff51..ddbcbdb 100644 --- a/include/bound/range.hpp +++ b/include/bound/range.hpp @@ -106,20 +106,52 @@ namespace bnd constexpr value_type operator*() const { - // value = Lower + index * Notch (always exact: lies on the grid). - bnd::detail::rational val = (G.Interval.Lower - + (bnd::detail::rational{index} * G.Notch).value()).value(); - return value_type{val}; + // value = Lower + index * Notch (always exact: lies on the grid). + // Integer-backed storages decode without the rational/assignment + // engine: for index storage the iterator index IS the raw (it stays in + // [0, NotchCount] and the sentinel slot is a numeric_limits extreme + // outside that span); integer-grid value storage is a multiply-add in + // raw space. Rational/fp raws keep the exact generic path. + if constexpr (bnd::detail::index_raw) + return value_type::from_raw( + static_cast(index)); + else if constexpr (bnd::detail::value_raw + && bnd::detail::abs_den(Notch.Denominator) == 1 + && bnd::detail::abs_den(Lower.Denominator) == 1) + { + constexpr imax notch_step = static_cast(Notch.Numerator); + return value_type::from_raw(static_cast( + bnd::detail::LowerImax + + static_cast(index) * notch_step)); + } + else + { + bnd::detail::rational val = (G.Interval.Lower + + (bnd::detail::rational{index} * G.Notch).value()).value(); + return value_type{val}; + } } constexpr value_type operator[](difference_type n) const { return *(*this + n); } // Advance / retreat: `remaining` is the position counter (advancing - // decreases it), so the <=> below flips the comparison. - constexpr iterator& operator++() { return *this += 1; } + // decreases it), so the <=> below flips the comparison. Single steps + // wrap by compare instead of the euclidean mod in `+= n` (index stays + // in [0, slot_count) — the mod would cost two divides per element). + constexpr iterator& operator++() + { + index = (index + 1 == slot_count) ? 0 : index + 1; + --remaining; + return *this; + } constexpr iterator operator++(int) { auto t = *this; ++*this; return t; } - constexpr iterator& operator--() { return *this -= 1; } + constexpr iterator& operator--() + { + index = (index == 0) ? slot_count - 1 : index - 1; + ++remaining; + return *this; + } constexpr iterator operator--(int) { auto t = *this; --*this; return t; } constexpr iterator& operator+=(difference_type n) @@ -152,11 +184,27 @@ namespace bnd constexpr bound_range(value_type start) { // Map a grid value back to its notch index: (start - Lower) / Notch. - // The result has integer denominator (start is on the grid) so the - // numerator is the index directly. - auto offset = ((detail::as_rational(start) - G.Interval.Lower) - / G.Notch).value(); - start_index_ = offset.Numerator; + // Same storage split as iterator::operator* — index raw already is the + // notch index; integer-grid value raw divides out the (integer) step. + if constexpr (detail::index_raw) + start_index_ = static_cast(start.raw()); + else if constexpr (detail::value_raw + && detail::abs_den(Notch.Denominator) == 1 + && detail::abs_den(Lower.Denominator) == 1) + { + constexpr imax notch_step = static_cast(Notch.Numerator); + start_index_ = static_cast( + (static_cast(start.raw()) - detail::LowerImax) + / notch_step); + } + else + { + // The result has integer denominator (start is on the grid) so the + // numerator is the index directly. + auto offset = ((detail::as_rational(start) - G.Interval.Lower) + / G.Notch).value(); + start_index_ = offset.Numerator; + } } constexpr iterator begin() const diff --git a/single_include/bound/bound.hpp b/single_include/bound/bound.hpp index 3c5ddec..5740378 100644 --- a/single_include/bound/bound.hpp +++ b/single_include/bound/bound.hpp @@ -5482,12 +5482,18 @@ namespace bnd::detail } template - static constexpr void store(L& lhs, R const& rhs, P&&) + static constexpr void store(L& lhs, R const& rhs, P&& policy) { if constexpr (fp_raw) // real target: raw IS the value — decode the source and snap to the dyadic // grid (the offset machinery below mis-encodes a double raw). lhs = L::from_raw(Grid.snap_double(as_double(rhs))); + else if constexpr (rational_raw) + // rational target: raw IS the value — snap the decoded source through + // the rational-rhs store (the offset machinery below would round the + // VALUE to a notch index and store that number as the raw). + assignment::store_checked(lhs, as_rational(rhs), policy, + no_action{}); else if constexpr (is_integer_mapping) { // exact: Factor and Offset have integer denominators, no rounding ambiguity @@ -7231,8 +7237,8 @@ namespace bnd private: // Out-of-range tail for raw-space compound arithmetic: dispatch on policy - // (clamp/wrap/sentinel/checked) and store back to `Raw`. Called from - // `operator+=(boundable)` only. + // (clamp/wrap/sentinel/checked) and store back to `Raw`. Called from the + // raw fast paths of `operator+=(boundable)` and `operator-=(boundable)`. constexpr bound& apply_raw_overflow(imax new_raw) { if constexpr (P & clamp) @@ -7291,7 +7297,45 @@ namespace bnd template constexpr bound& operator-=(R const& rhs) - { return *this += (-rhs); } + { + // Raw-space fast path, the subtraction mirror of +='s: with equal + // notches, raw(v_l − v_r) = raw_l − raw_r − bias, where the bias is + // Lower/Notch for an index-raw rhs (its raw is Lower-relative) and 0 + // for a value-raw rhs. Delegating to `+= (-rhs)` instead shifts R's + // Lower by negation and defeats +='s raw path for index-backed grids. + if constexpr (!detail::rational_raw && !detail::rational_raw + && !detail::fp_raw && !detail::fp_raw + && Notch != 0 && Notch == Notch + && (!detail::index_raw + || ((Lower / Notch).has_value() + && detail::abs_den((*(Lower / Notch)).Denominator) == 1))) + { + constexpr imax bias = [] { + if constexpr (detail::index_raw) + { + constexpr auto quotient = *(Lower / Notch); + return (quotient.Denominator < 0) + ? -static_cast(quotient.Numerator) + : static_cast(quotient.Numerator); + } + else + return imax{0}; + }(); + if constexpr (P & (clamp | wrap | checked | sentinel)) + { + imax new_raw = detail::raw_imax(*this) - detail::raw_imax(rhs) - bias; + if (new_raw < detail::RawLo || new_raw > detail::RawHi) + return apply_raw_overflow(new_raw); + Raw = detail::raw_cast(new_raw); + } + else + Raw = detail::raw_cast( + detail::raw_imax(*this) - detail::raw_imax(rhs) - bias); + return *this; + } + else + return *this += (-rhs); + } template A> constexpr bound& operator-=(A const& rhs) @@ -7463,11 +7507,53 @@ namespace bnd return detail::as_rational(lhs) == detail::as_rational(rhs); } + namespace detail + { + // bound ⋈ integral scalar without the rational decode: with the positive + // notch n/d, value ⋈ c ⟺ (bias + raw)·n ⋈ c·d — both sides exact + // integers (bias + raw is the signed value index, exact ordering AND + // equality since c·d is exact too). Eligible when both cross terms + // provably fit imax for every representable c of type A. + template + inline constexpr bool scalar_index_cmp_fits = []{ + if constexpr (!std::integral || !index_raw || !index_cmp_fits) + return false; + else + { + constexpr umax cap = static_cast(std::numeric_limits::max()); + constexpr umax notch_num = Notch.Numerator; + constexpr umax notch_den = static_cast(Notch.Denominator); // Notch > 0 + constexpr umax index_mag = []{ + constexpr auto lo = *(Lower / Notch); + constexpr auto hi = *(Upper / Notch); + return lo.Numerator > hi.Numerator ? lo.Numerator : hi.Numerator; + }(); + constexpr umax scalar_mag = []{ + umax mag = static_cast(std::numeric_limits::max()); + if constexpr (std::signed_integral) + { + umax min_mag = static_cast( + -(std::numeric_limits::min() + 1)) + 1; + if (min_mag > mag) mag = min_mag; + } + return mag; + }(); + umax product; + return !mul_overflow(index_mag, notch_num, &product) && product <= cap + && !mul_overflow(scalar_mag, notch_den, &product) && product <= cap; + } + }(); + } + template constexpr auto operator<=>(B const& lhs, A rhs) { if constexpr (detail::value_raw) return detail::raw_imax(lhs) <=> static_cast(rhs); + else if constexpr (detail::scalar_index_cmp_fits) + return (detail::index_cmp_bias + detail::raw_imax(lhs)) + * static_cast(Notch.Numerator) + <=> static_cast(rhs) * Notch.Denominator; else return detail::as_rational(lhs) <=> bnd::detail::rational{rhs}; } @@ -7477,6 +7563,10 @@ namespace bnd { if constexpr (detail::value_raw) return detail::raw_imax(lhs) == static_cast(rhs); + else if constexpr (detail::scalar_index_cmp_fits) + return (detail::index_cmp_bias + detail::raw_imax(lhs)) + * static_cast(Notch.Numerator) + == static_cast(rhs) * Notch.Denominator; else return detail::as_rational(lhs) == bnd::detail::rational{rhs}; } @@ -8285,20 +8375,52 @@ namespace bnd constexpr value_type operator*() const { - // value = Lower + index * Notch (always exact: lies on the grid). - bnd::detail::rational val = (G.Interval.Lower - + (bnd::detail::rational{index} * G.Notch).value()).value(); - return value_type{val}; + // value = Lower + index * Notch (always exact: lies on the grid). + // Integer-backed storages decode without the rational/assignment + // engine: for index storage the iterator index IS the raw (it stays in + // [0, NotchCount] and the sentinel slot is a numeric_limits extreme + // outside that span); integer-grid value storage is a multiply-add in + // raw space. Rational/fp raws keep the exact generic path. + if constexpr (bnd::detail::index_raw) + return value_type::from_raw( + static_cast(index)); + else if constexpr (bnd::detail::value_raw + && bnd::detail::abs_den(Notch.Denominator) == 1 + && bnd::detail::abs_den(Lower.Denominator) == 1) + { + constexpr imax notch_step = static_cast(Notch.Numerator); + return value_type::from_raw(static_cast( + bnd::detail::LowerImax + + static_cast(index) * notch_step)); + } + else + { + bnd::detail::rational val = (G.Interval.Lower + + (bnd::detail::rational{index} * G.Notch).value()).value(); + return value_type{val}; + } } constexpr value_type operator[](difference_type n) const { return *(*this + n); } // Advance / retreat: `remaining` is the position counter (advancing - // decreases it), so the <=> below flips the comparison. - constexpr iterator& operator++() { return *this += 1; } + // decreases it), so the <=> below flips the comparison. Single steps + // wrap by compare instead of the euclidean mod in `+= n` (index stays + // in [0, slot_count) — the mod would cost two divides per element). + constexpr iterator& operator++() + { + index = (index + 1 == slot_count) ? 0 : index + 1; + --remaining; + return *this; + } constexpr iterator operator++(int) { auto t = *this; ++*this; return t; } - constexpr iterator& operator--() { return *this -= 1; } + constexpr iterator& operator--() + { + index = (index == 0) ? slot_count - 1 : index - 1; + ++remaining; + return *this; + } constexpr iterator operator--(int) { auto t = *this; --*this; return t; } constexpr iterator& operator+=(difference_type n) @@ -8331,11 +8453,27 @@ namespace bnd constexpr bound_range(value_type start) { // Map a grid value back to its notch index: (start - Lower) / Notch. - // The result has integer denominator (start is on the grid) so the - // numerator is the index directly. - auto offset = ((detail::as_rational(start) - G.Interval.Lower) - / G.Notch).value(); - start_index_ = offset.Numerator; + // Same storage split as iterator::operator* — index raw already is the + // notch index; integer-grid value raw divides out the (integer) step. + if constexpr (detail::index_raw) + start_index_ = static_cast(start.raw()); + else if constexpr (detail::value_raw + && detail::abs_den(Notch.Denominator) == 1 + && detail::abs_den(Lower.Denominator) == 1) + { + constexpr imax notch_step = static_cast(Notch.Numerator); + start_index_ = static_cast( + (static_cast(start.raw()) - detail::LowerImax) + / notch_step); + } + else + { + // The result has integer denominator (start is on the grid) so the + // numerator is the index directly. + auto offset = ((detail::as_rational(start) - G.Interval.Lower) + / G.Notch).value(); + start_index_ = offset.Numerator; + } } constexpr iterator begin() const diff --git a/tests/bench.cpp b/tests/bench.cpp index 3418bd8..9387d27 100644 --- a/tests/bench.cpp +++ b/tests/bench.cpp @@ -164,6 +164,20 @@ static void bench_scalar_u8() }); finish(add); + static const scalar_inputs sub_in{100, 190, 0, 90}; // difference in [10, 190] + auto sub = group("sub (u8 [0,200])"); + sub.run("native uint8", [&] { + ++i; + auto c = static_cast(sub_in.a[i & kMask] - sub_in.b[i & kMask]); + doNotOptimizeAway(c); + }); + sub.run("bound", [&] { + ++i; + u200 a(sub_in.a[i & kMask]), b(sub_in.b[i & kMask]); + doNotOptimizeAway((a - b).raw()); + }); + finish(sub); + static const scalar_inputs min{1, 14, 1, 14}; // product ≤ 196 auto mul = group("mul (u8 [0,200])"); mul.run("native uint8", [&] { @@ -201,6 +215,28 @@ static void bench_scalar_u8() doNotOptimizeAway(div(a, b, truncated)->raw()); }); finish(dv); + + // divisor grid [1,9] excludes zero -> `%` returns a plain bound; a [0,200] + // divisor keeps the zero check and the optional vocabulary. + using d9 = bound<{1, 9}, unsafe>; + auto md = group("mod (u8 [20,200] % [1,9])"); + md.run("native %", [&] { + ++i; + auto c = static_cast(din.a[i & kMask] % din.b[i & kMask]); + doNotOptimizeAway(c); + }); + md.run("bound % (divisor excludes 0)", [&] { + ++i; + u200 a(din.a[i & kMask]); + d9 b(din.b[i & kMask]); + doNotOptimizeAway((a % b).raw()); + }); + md.run("bound mod(a, b, truncated) (zero-checked)", [&] { + ++i; + u200 a(din.a[i & kMask]), b(din.b[i & kMask]); + doNotOptimizeAway(mod(a, b, truncated)->raw()); + }); + finish(md); } //--------------------------------------------------------------------------- @@ -221,8 +257,77 @@ static void bench_compound() }); finish(inc); - static const scalar_inputs din{20, 200, 2, 9}; + static const scalar_inputs sub_in{100, 190, 0, 90}; // difference in [10, 190] std::size_t i = 0; + + auto sba = group("compound -= (u8 [0,200])"); + sba.run("native -=", [&] { + ++i; + auto a = static_cast(sub_in.a[i & kMask]); + a = static_cast(a - static_cast(sub_in.b[i & kMask])); + doNotOptimizeAway(a); + }); + sba.run("bound -=", [&] { + ++i; + u200 a(sub_in.a[i & kMask]); + a -= u200(sub_in.b[i & kMask]); + doNotOptimizeAway(a.raw()); + }); + finish(sba); + + // Q8.8 is index-backed: negating the rhs shifts its Lower, so `-= (+= -rhs)` + // cannot ride the raw-level += fast path — the row this table pins. + using fp88 = bound<{{0, 255}, 1.0 / 256}, unsafe>; + auto sbq = group("compound -= (Q8.8)"); + sbq.run("native int32 -=", [&] { + ++i; + std::int32_t a = sub_in.a[i & kMask] << 8; + a -= sub_in.b[i & kMask] << 8; + doNotOptimizeAway(a); + }); + sbq.run("bound -=", [&] { + ++i; + fp88 a(sub_in.a[i & kMask]); + a -= fp88(sub_in.b[i & kMask]); + doNotOptimizeAway(a.raw()); + }); + finish(sbq); + + static const scalar_inputs min_in{1, 14, 1, 14}; // product ≤ 196 + auto mla = group("compound *= (u8 [0,200])"); + mla.run("native *=", [&] { + ++i; + auto a = static_cast(min_in.a[i & kMask]); + a = static_cast(a * static_cast(min_in.b[i & kMask])); + doNotOptimizeAway(a); + }); + mla.run("bound *=", [&] { + ++i; + u200 a(min_in.a[i & kMask]); + a *= u200(min_in.b[i & kMask]); + doNotOptimizeAway(a.raw()); + }); + finish(mla); + + // rational rhs takes the exact rational branch by design (see the + // known-slow-paths note); this row documents its cost. + static const scalar_inputs qin{0, 90, 0, 90}; + auto rga = group("compound += rational (Q8.8)"); + rga.run("native int32 += k", [&] { + ++i; + std::int32_t a = qin.a[i & kMask] << 8; + a += static_cast(i & 63); + doNotOptimizeAway(a); + }); + rga.run("bound += rational{k, 256}", [&] { + ++i; + fp88 a(qin.a[i & kMask]); + a += rational{static_cast(i & 63), 256}; + doNotOptimizeAway(a.raw()); + }); + finish(rga); + + static const scalar_inputs din{20, 200, 2, 9}; auto dva = group("compound /= (u8 [0,200])"); dva.run("native /=", [&] { ++i; @@ -319,6 +424,32 @@ static void bench_signed() }); finish(mul); + auto sb = group("sub (signed [-100k,100k])"); + sb.run("native int", [&] { + ++i; + int c = in.a[i & kMask] - in.b[i & kMask]; + doNotOptimizeAway(c); + }); + sb.run("bound", [&] { + ++i; + s100k a(in.a[i & kMask]), b(in.b[i & kMask]); + doNotOptimizeAway((a - b).raw()); + }); + finish(sb); + + auto ng = group("negate (signed [-100k,100k])"); + ng.run("native int", [&] { + ++i; + int c = -in.a[i & kMask]; + doNotOptimizeAway(c); + }); + ng.run("bound unary -", [&] { + ++i; + s100k a(in.a[i & kMask]); + doNotOptimizeAway((-a).raw()); + }); + finish(ng); + // mixed integer-grid + quarter-notch-grid add (Tier-3 fast path): the // native pairing is the same math on a hand-scaled 1/4 fixed-point lattice. using whole = bound<{0, 100}, unsafe>; @@ -407,6 +538,20 @@ static void bench_fixed_point() }); finish(add); + static const scalar_inputs sub_in{100, 190, 0, 90}; + auto subq = group("Q8.8 sub"); + subq.run("native", [&] { + ++i; + std::int32_t a = sub_in.a[i & kMask] << 8, b = sub_in.b[i & kMask] << 8; + doNotOptimizeAway(a - b); + }); + subq.run("bound", [&] { + ++i; + fp a(sub_in.a[i & kMask]), b(sub_in.b[i & kMask]); + doNotOptimizeAway((a - b).raw()); + }); + finish(subq); + static const scalar_inputs min{1, 15, 1, 15}; auto mul = group("Q8.8 mul"); mul.run("native (a*b)>>8", [&] { @@ -592,6 +737,35 @@ static void bench_store_convert() }); finish(cmp); + // equality — same-grid raw compare, plus the bound-vs-raw-scalar overloads + // (value-raw compares in imax; index-raw Q8.8 goes through rational). + using fp_u = bound<{{0, 255}, 1.0 / 256}, unsafe>; + std::vector qv(kMask + 1); + for (std::size_t j = 0; j <= kMask; ++j) + qv[j] = static_cast(j % 250); + auto eq = group("comparison =="); + eq.run("native int16", [&] { + ++i; + bool r = ni[i & kMask] == ni[(i + 7) & kMask]; + doNotOptimizeAway(r); + }); + eq.run("bound same grid", [&] { + ++i; + bool r = bi[i & kMask] == bi[(i + 7) & kMask]; + doNotOptimizeAway(r); + }); + eq.run("bound == int scalar (value raw)", [&] { + ++i; + bool r = bi[i & kMask] == 42; + doNotOptimizeAway(r); + }); + eq.run("bound Q8.8 == int scalar (index raw)", [&] { + ++i; + bool r = qv[i & kMask] == 42; + doNotOptimizeAway(r); + }); + finish(eq); + // casts auto casts = group("casts (s9k value into u200)"); casts.run("native clamp", [&] { @@ -613,6 +787,69 @@ static void bench_store_convert() }); finish(casts); + auto wrp = group("wrap_cast (s9k value into u200)"); + wrp.run("native euclidean mod", [&] { + ++i; + int v = static_cast(i % 9000) - 500; + auto c = static_cast((v % 201 + 201) % 201); + doNotOptimizeAway(c); + }); + wrp.run("wrap_cast", [&] { + ++i; + s9k v(static_cast(i % 9000) - 500); + doNotOptimizeAway(wrap_cast(v).raw()); + }); + finish(wrp); + + // sentinel policy: out-of-range store yields the empty slot (no handler). + using u200s = bound<{0, 200}, sentinel>; + auto sst = group("sentinel store (50% out of range)"); + sst.run("native branch + flag value", [&] { + ++i; + int v = static_cast(i % 400); + auto c = static_cast(v <= 200 ? v : 255); + doNotOptimizeAway(c); + }); + sst.run("bound", [&] { + ++i; + u200s v(static_cast(i % 400)); + doNotOptimizeAway(v.raw()); + }); + finish(sst); + + // conversions OUT of bound — the API-boundary direction (stores are above). + using dyadic_real = bound<{{-8, 8}, notch<1, 16384>}, round_nearest | real>; + std::vector rv(kMask + 1); + std::vector uv(kMask + 1); + for (std::size_t j = 0; j <= kMask; ++j) + { + rv[j] = rational{static_cast(j * 16) - 8192, 16384}; + uv[j] = static_cast(j % 201); + } + auto conv = group("convert out (bound -> scalar)"); + conv.run("native int passthrough", [&] { + ++i; + int v = ni[i & kMask]; + doNotOptimizeAway(v); + }); + conv.run("static_cast (integer grid)", [&] { + ++i; + doNotOptimizeAway(static_cast(bi[i & kMask])); + }); + conv.run("to (expected)", [&] { + ++i; + doNotOptimizeAway(*uv[i & kMask].to()); + }); + conv.run("static_cast (f64-backed)", [&] { + ++i; + doNotOptimizeAway(static_cast(rv[i & kMask])); + }); + conv.run("numerator() (Q8.8)", [&] { + ++i; + doNotOptimizeAway(qv[i & kMask].numerator()); + }); + finish(conv); + // dot static const scalar_inputs din{-90, 90, -90, 90}; auto dg = group("dot (2D, signed)"); @@ -632,6 +869,367 @@ static void bench_store_convert() finish(dg); } +//--------------------------------------------------------------------------- +// generic helpers — min/max (same + mixed grid), midpoint, lerp, cross, +// add_all/mul_all. +//--------------------------------------------------------------------------- +static void bench_helpers() +{ + static const scalar_inputs in{-90, 90, -90, 90}; + std::size_t i = 0; + using c100 = bound<{-100, 100}, unsafe>; + + auto mn = group("min / max (same grid)"); + mn.run("native std::min", [&] { + ++i; + doNotOptimizeAway(std::min(in.a[i & kMask], in.b[i & kMask])); + }); + mn.run("bnd::min", [&] { + ++i; + c100 a(in.a[i & kMask]), b(in.b[i & kMask]); + doNotOptimizeAway(bnd::min(a, b).raw()); + }); + mn.run("bnd::max", [&] { + ++i; + c100 a(in.a[i & kMask]), b(in.b[i & kMask]); + doNotOptimizeAway(bnd::max(a, b).raw()); + }); + finish(mn); + + // mixed grids: both operands convert into the hull type ([0,100] 1/10 grid). + using narrow = bound<{0, 100}, unsafe>; + using tenths = bound<{{0, 100}, notch<1, 10>}, unsafe>; + static const scalar_inputs pin{0, 100, 0, 100}; + std::vector tv(kMask + 1); + for (std::size_t j = 0; j <= kMask; ++j) + tv[j] = rational{static_cast((j * 13 + 5) % 1001), 10}; + auto mm = group("min (mixed grids -> hull)"); + mm.run("native int on 1/10 lattice", [&] { + ++i; + int a = pin.a[i & kMask] * 10, b = static_cast((i * 13 + 5) % 1001); + doNotOptimizeAway(std::min(a, b)); + }); + mm.run("bnd::min(narrow, tenths)", [&] { + ++i; + narrow a(pin.a[i & kMask]); + doNotOptimizeAway(bnd::min(a, tv[i & kMask]).raw()); + }); + finish(mm); + + static const scalar_inputs uin{0, 90, 0, 90}; + auto mp = group("midpoint (u8 [0,200])"); + mp.run("native (a+b)/2", [&] { + ++i; + doNotOptimizeAway((uin.a[i & kMask] + uin.b[i & kMask]) / 2); + }); + mp.run("bnd::midpoint", [&] { + ++i; + u200 a(uin.a[i & kMask]), b(uin.b[i & kMask]); + doNotOptimizeAway(bnd::midpoint(a, b).raw()); + }); + finish(mp); + + // lerp on Q8.8 endpoints with a [0,1] 1/256 parameter. + using fp88 = bound<{{0, 255}, 1.0 / 256}, unsafe>; + using t256 = bound<{{0, 1}, notch<1, 256>}, unsafe>; + auto lp = group("lerp (Q8.8, t in [0,1])"); + lp.run("native a + (((b-a)*t)>>8)", [&] { + ++i; + std::int32_t a = uin.a[i & kMask] << 8, b = uin.b[i & kMask] << 8; + std::int32_t t = static_cast(i & 255); + doNotOptimizeAway(a + (((b - a) * t) >> 8)); + }); + lp.run("bnd::lerp", [&] { + ++i; + fp88 a(uin.a[i & kMask]), b(uin.b[i & kMask]); + t256 t = t256::from_raw(static_cast(i & 255)); + doNotOptimizeAway(bnd::lerp(a, b, t).raw()); + }); + finish(lp); + + auto cg = group("cross (2D, signed)"); + cg.run("native int", [&] { + ++i; + int ax = in.a[i & kMask], ay = in.b[i & kMask]; + int bx = in.b[(i + 3) & kMask], by = in.a[(i + 5) & kMask]; + doNotOptimizeAway(ax * by - ay * bx); + }); + cg.run("bnd::cross", [&] { + ++i; + c100 ax(in.a[i & kMask]), ay(in.b[i & kMask]); + c100 bx(in.b[(i + 3) & kMask]), by(in.a[(i + 5) & kMask]); + doNotOptimizeAway(bnd::cross(ax, ay, bx, by).raw()); + }); + finish(cg); + + auto aa = group("add_all (4 operands)"); + aa.run("native a+b+c+d", [&] { + ++i; + doNotOptimizeAway(in.a[i & kMask] + in.b[i & kMask] + + in.a[(i + 3) & kMask] + in.b[(i + 5) & kMask]); + }); + aa.run("bnd::add_all", [&] { + ++i; + c100 a(in.a[i & kMask]), b(in.b[i & kMask]); + c100 c(in.a[(i + 3) & kMask]), d(in.b[(i + 5) & kMask]); + doNotOptimizeAway(add_all(a, b, c, d).raw()); + }); + finish(aa); + + using c15 = bound<{1, 15}, unsafe>; + static const scalar_inputs min_in{1, 14, 1, 14}; + auto ma = group("mul_all (3 operands)"); + ma.run("native a*b*c", [&] { + ++i; + doNotOptimizeAway(min_in.a[i & kMask] * min_in.b[i & kMask] + * min_in.a[(i + 3) & kMask]); + }); + ma.run("bnd::mul_all", [&] { + ++i; + c15 a(min_in.a[i & kMask]), b(min_in.b[i & kMask]), c(min_in.a[(i + 3) & kMask]); + doNotOptimizeAway(mul_all(a, b, c).raw()); + }); + finish(ma); +} + +//--------------------------------------------------------------------------- +// f64-backed (real) arithmetic — one double op behind the fp fast arm; +// parity with native double is the expectation being pinned. +//--------------------------------------------------------------------------- +static void bench_fp_backed() +{ + using dyadic_real = bound<{{-8, 8}, notch<1, 16384>}, round_nearest | real>; + std::vector va(kMask + 1), vb(kMask + 1); + std::vector da(kMask + 1), db(kMask + 1); + for (std::size_t j = 0; j <= kMask; ++j) + { + rational qa{static_cast((j * 16) & 0x3FFF) - 8192, 16384}; + rational qb{static_cast((j * 48 + 16) & 0x3FFF) - 8192, 16384}; + va[j] = qa; vb[j] = qb; + da[j] = static_cast(qa); db[j] = static_cast(qb); + } + std::size_t i = 0; + + auto add = group("f64-backed add (dyadic 1/16384 grid)"); + add.run("native double", [&] { + ++i; + doNotOptimizeAway(da[i & kMask] + db[i & kMask]); + }); + add.run("bound", [&] { + ++i; + doNotOptimizeAway((va[i & kMask] + vb[i & kMask]).raw()); + }); + finish(add); + + auto mul = group("f64-backed mul (dyadic 1/16384 grid)"); + mul.run("native double", [&] { + ++i; + doNotOptimizeAway(da[i & kMask] * db[i & kMask]); + }); + mul.run("bound", [&] { + ++i; + doNotOptimizeAway((va[i & kMask] * vb[i & kMask]).raw()); + }); + finish(mul); +} + +//--------------------------------------------------------------------------- +// rational computations — the exact path. Native baseline is a hand-rolled +// reduced int64 fraction (cross-multiply + gcd), i.e. the code a user would +// write for the same exactness guarantee. +//--------------------------------------------------------------------------- +namespace +{ + struct fraction64 + { + std::int64_t num{0}, den{1}; + }; + + fraction64 fraction_reduce(std::int64_t n, std::int64_t d) + { + if (d < 0) { n = -n; d = -d; } + std::int64_t g = std::gcd(n < 0 ? -n : n, d); + if (g == 0) g = 1; + return {n / g, d / g}; + } + + fraction64 fraction_add(fraction64 a, fraction64 b) + { return fraction_reduce(a.num * b.den + b.num * a.den, a.den * b.den); } + + fraction64 fraction_mul(fraction64 a, fraction64 b) + { return fraction_reduce(a.num * b.num, a.den * b.den); } + + fraction64 fraction_div(fraction64 a, fraction64 b) + { return fraction_reduce(a.num * b.den, a.den * b.num); } +} + +static void bench_rational() +{ + using exact_grid = bound<{{-8, 8}, notch<1, 1024>}, exact>; + std::vector ea(kMask + 1), eb(kMask + 1), ep(kMask + 1); + std::vector qa(kMask + 1), qb(kMask + 1); + std::vector fa(kMask + 1), fb(kMask + 1), fp_(kMask + 1); + std::vector da(kMask + 1), db(kMask + 1); + std::vector ib(kMask + 1); + for (std::size_t j = 0; j <= kMask; ++j) + { + imax ka = static_cast((j * 7 + 3) % 16384) - 8192; + imax kb = static_cast((j * 13 + 5) % 16384) - 8192; + imax kp = static_cast((j * 11 + 7) % 8191) + 1; // positive divisor + ea[j] = rational{ka, 1024}; eb[j] = rational{kb, 1024}; + ep[j] = rational{kp, 1024}; + qa[j] = rational{ka, 1024}; qb[j] = rational{kb, 1024}; + fa[j] = fraction_reduce(ka, 1024); fb[j] = fraction_reduce(kb, 1024); + fp_[j] = fraction_reduce(kp, 1024); + da[j] = static_cast(qa[j]); db[j] = static_cast(qb[j]); + ib[j] = static_cast(j % 200); + } + std::size_t i = 0; + constexpr std::uint64_t kIters = 100'000; + + auto add = group("exact-backed add (rational storage)", kIters); + add.run("native fraction", [&] { + ++i; + doNotOptimizeAway(fraction_add(fa[i & kMask], fb[i & kMask]).num); + }); + add.run("std double (reference)", [&] { + ++i; + doNotOptimizeAway(da[i & kMask] + db[i & kMask]); + }); + add.run("bound", [&] { + ++i; + doNotOptimizeAway((ea[i & kMask] + eb[i & kMask]).raw().Numerator); + }); + finish(add); + + auto mul = group("exact-backed mul (rational storage)", kIters); + mul.run("native fraction", [&] { + ++i; + doNotOptimizeAway(fraction_mul(fa[i & kMask], fb[i & kMask]).num); + }); + mul.run("bound", [&] { + ++i; + doNotOptimizeAway((ea[i & kMask] * eb[i & kMask]).raw().Numerator); + }); + finish(mul); + + auto dv = group("exact-backed div (rational storage)", kIters); + dv.run("native fraction", [&] { + ++i; + doNotOptimizeAway(fraction_div(fa[i & kMask], fp_[i & kMask]).num); + }); + dv.run("bound", [&] { + ++i; + doNotOptimizeAway((ea[i & kMask] / ep[i & kMask])->raw().Numerator); + }); + finish(dv); + + auto cp = group("exact-backed compare <", kIters); + cp.run("native fraction cross-multiply", [&] { + ++i; + fraction64 a = fa[i & kMask], b = fb[i & kMask]; + doNotOptimizeAway(a.num * b.den < b.num * a.den); + }); + cp.run("bound", [&] { + ++i; + doNotOptimizeAway(ea[i & kMask] < eb[i & kMask]); + }); + finish(cp); + + // rational-raw operand meets an integer-backed bound: the residual rational + // branch the Tier-3 integer gates deliberately exclude. + auto mx = group("mixed exact + integer-grid add", kIters); + mx.run("native fraction + int", [&] { + ++i; + fraction64 a = fa[i & kMask]; + std::int64_t n = static_cast(ib[i & kMask].raw()); + doNotOptimizeAway(fraction_add(a, fraction64{n, 1}).num); + }); + mx.run("bound + bound integer grid", [&] { + ++i; + doNotOptimizeAway((ea[i & kMask] + ib[i & kMask]).raw().Numerator); + }); + finish(mx); + + // detail::rational directly — the primitive every slow path sits on. + auto ra = group("rational engine add", kIters); + ra.run("native fraction", [&] { + ++i; + doNotOptimizeAway(fraction_add(fa[i & kMask], fb[i & kMask]).num); + }); + ra.run("detail::rational", [&] { + ++i; + doNotOptimizeAway((qa[i & kMask] + qb[i & kMask])->Numerator); + }); + finish(ra); + + auto rm = group("rational engine mul", kIters); + rm.run("native fraction", [&] { + ++i; + doNotOptimizeAway(fraction_mul(fa[i & kMask], fb[i & kMask]).num); + }); + rm.run("detail::rational", [&] { + ++i; + doNotOptimizeAway((qa[i & kMask] * qb[i & kMask])->Numerator); + }); + finish(rm); + + auto rc = group("rational engine compare <", kIters); + rc.run("native fraction cross-multiply", [&] { + ++i; + fraction64 a = fa[i & kMask], b = fb[i & kMask]; + doNotOptimizeAway(a.num * b.den < b.num * a.den); + }); + rc.run("detail::rational", [&] { + ++i; + doNotOptimizeAway(qa[i & kMask] < qb[i & kMask]); + }); + finish(rc); +} + +//--------------------------------------------------------------------------- +// bound_range iteration — materialize every grid value into an array (a sum +// would let the compiler close-form the whole loop on both sides). +//--------------------------------------------------------------------------- +static void bench_range() +{ + using int_range = bound_range<{0, 999}, unsafe>; + using frac_range = bound_range<{{0, 4}, notch<1, 256>}, unsafe>; // 1025 slots + + std::vector out_n(1000); + std::vector out_b(1000); + auto rg = group("iterate bound_range 1000 (per element)", 300); + rg.batch(1000); + rg.run("native counting loop", [&] { + std::size_t k = 0; + for (int v = 0; v <= 999; ++v) out_n[k++] = static_cast(v); + doNotOptimizeAway(out_n[0]); + }); + rg.run("bound_range integer grid", [&] { + std::size_t k = 0; + for (auto b : int_range{}) out_b[k++] = static_cast(b.raw()); + doNotOptimizeAway(out_b[0]); + }); + finish(rg); + + std::vector qout_n(frac_range::slot_count); + std::vector qout_b(frac_range::slot_count); + auto rq = group("iterate bound_range Q-grid 1/256 (per element)", 300); + rq.batch(frac_range::slot_count); + rq.run("native counting loop", [&] { + std::size_t k = 0; + for (int v = 0; v < static_cast(frac_range::slot_count); ++v) + qout_n[k++] = static_cast(v); + doNotOptimizeAway(qout_n[0]); + }); + rq.run("bound_range 1/256 grid", [&] { + std::size_t k = 0; + for (auto b : frac_range{}) qout_b[k++] = static_cast(b.raw()); + doNotOptimizeAway(qout_b[0]); + }); + finish(rq); +} + //--------------------------------------------------------------------------- // cmath — bound math engines vs std double, per function. Inputs pre-built // (grids match tests/test_cmath.cpp). @@ -651,6 +1249,8 @@ static void bench_cmath() using atan2_in_t= bound<{{-1, 1}, notch<1, 16384>}, round_nearest | real>; using fmod_x_t = bound<{{-8, 8}, notch<1, 16384>}, round_nearest>; using fmod_y_t = bound<{{0.25_r, 4}, notch<1, 16384>}, round_nearest>; + using pow_b_t = bound<{{1, 16}, notch<1, 65536>}, round_nearest | real>; + using pow_e_t = bound<{{-2, 2}, notch<1, 16384>}, round_nearest | real>; constexpr std::size_t M = 4096; std::vector v_alg, v_alg2; std::vector v_sqrt; @@ -660,7 +1260,9 @@ static void bench_cmath() std::vector v_angf; std::vector v_tan; std::vector v_aty, v_atx; std::vector v_fmx; std::vector v_fmy; - std::vector d_qs, d_q, d_log2, d_log, d_tan, d_aty, d_atx, d_fmy; + std::vector v_powb; std::vector v_powe; + std::vector d_qs, d_q, d_log2, d_log, d_tan, d_aty, d_atx, d_fmy, + d_powb, d_powe; for (std::size_t j = 0; j < M; ++j) { imax k = static_cast((j * 16) & 0xFFFF); @@ -681,6 +1283,10 @@ static void bench_cmath() v_tan.push_back(tan_in_t{rt}); v_aty.push_back(atan2_in_t{ry}); v_atx.push_back(atan2_in_t{rx}); v_fmx.push_back(fmod_x_t{qs}); v_fmy.push_back(fmod_y_t{rfy}); + rational rpb{65536 + (k % 65536), 65536}; // base in [1, 2) + v_powb.push_back(pow_b_t{rpb}); v_powe.push_back(pow_e_t{qs}); + d_powb.push_back(static_cast(rpb)); + d_powe.push_back(static_cast(qs)); d_qs.push_back(static_cast(qs)); d_q.push_back(static_cast(q)); d_log2.push_back(static_cast(rl2)); d_log.push_back(static_cast(rl)); d_tan.push_back(static_cast(rt)); d_aty.push_back(static_cast(ry)); @@ -720,6 +1326,16 @@ static void bench_cmath() BND_CMATH_GROUP("math: cbrt", std::cbrt(d_qs[i & J]), bnd::math::cbrt(v_alg[i & J]).raw()) BND_CMATH_GROUP("math: hypot", std::hypot(d_qs[i & J], d_q[i & J]), bnd::math::hypot(v_alg[i & J], v_alg2[i & J]).raw()) + BND_CMATH_GROUP("math: ceil", std::ceil(d_qs[i & J]), bnd::math::ceil(v_alg[i & J]).raw()) + BND_CMATH_GROUP("math: atan", std::atan(d_aty[i & J]), bnd::math::atan(v_aty[i & J]).raw()) + BND_CMATH_GROUP("math: acos", std::acos(d_aty[i & J]), bnd::math::acos(v_aty[i & J]).raw()) + BND_CMATH_GROUP("math: sinh", std::sinh(d_qs[i & J]), bnd::math::sinh(v_exp[i & J]).raw()) + BND_CMATH_GROUP("math: log10", std::log10(d_log[i & J]),bnd::math::log10(v_log[i & J]).raw()) + BND_CMATH_GROUP("math: pown<3>", d_qs[i & J] * d_qs[i & J] * d_qs[i & J], + bnd::math::pown<3>(v_alg[i & J]).raw()) + BND_CMATH_GROUP("math: pow (expected)", + std::pow(d_powb[i & J], d_powe[i & J]), + (*bnd::math::pow(v_powb[i & J], v_powe[i & J])).raw()) #undef BND_CMATH_GROUP #ifndef BND_MATH_FIXED @@ -911,11 +1527,14 @@ int main(int argc, char** argv) "— the ratios are the stable signal. Instruction/cycle/branch columns\n" "appear when the kernel grants perf-counter access.\n\n" "Known slow paths (measured in the tables below, by design):\n" - "arithmetic with a rational-raw operand takes the exact rational path\n" - "(the mixed integer/notch-offset add and cross-grid non-integer stores\n" - "now have folded integer fast paths, gated on imax-safe spans);\n" - "`bound` element-wise loops keep a per-element range check —\n" - "prefer `bnd::sum`'s bulk check, which validates the total instead.\n\n"; + "arithmetic with a rational-raw operand takes the exact rational path —\n" + "its cost is measured directly in the exact-backed / rational-engine\n" + "tables against a hand-rolled reduced int64 fraction (the mixed\n" + "integer/notch-offset add and cross-grid non-integer stores have folded\n" + "integer fast paths, gated on imax-safe spans); a rational RHS in a\n" + "compound assign takes the same exact branch; `bound`\n" + "element-wise loops keep a per-element range check — prefer `bnd::sum`'s\n" + "bulk check, which validates the total instead.\n\n"; bench_scalar_u8(); bench_compound(); @@ -923,6 +1542,10 @@ int main(int argc, char** argv) bench_signed(); bench_fixed_point(); bench_store_convert(); + bench_helpers(); + bench_fp_backed(); + bench_rational(); + bench_range(); bench_cmath(); bench_algorithms(); return 0; diff --git a/tests/check_codegen.sh b/tests/check_codegen.sh index 781095a..e292b4c 100755 --- a/tests/check_codegen.sh +++ b/tests/check_codegen.sh @@ -25,7 +25,7 @@ fail() { echo "CODEGEN GUARD FAILED: $1"; echo "----- disassembly -----"; echo " # addition-dispatch bug, where an operand silently dropped to the value path). # Checked on both the scalar and the loop body, so the fallback is caught # whether or not the loop happens to vectorize. -for fn in bnd_perf_add_fast bnd_perf_add_loop bnd_perf_mul_fast; do +for fn in bnd_perf_add_fast bnd_perf_add_loop bnd_perf_mul_fast bnd_perf_sub_compound bnd_perf_fp_add bnd_perf_range_sum; do body="$(printf '%s\n' "$DIS" | sed -n "/<$fn>:/,/^\$/p")" [ -n "$body" ] || fail "could not find $fn in the disassembly" "$DIS" if printf '%s\n' "$body" | grep -qE '\bcall[a-z]*\b'; then diff --git a/tests/perf_codegen.cpp b/tests/perf_codegen.cpp index 12a5b6b..75bf286 100644 --- a/tests/perf_codegen.cpp +++ b/tests/perf_codegen.cpp @@ -52,3 +52,40 @@ extern "C" long bnd_perf_mul_fast(long a, long b) M y = M::from_raw(static_cast(b)); return static_cast((x * y).raw()); } + +// f64-backed fast arm: add on a dyadic `real` grid must lower to a bare +// double add (one addsd) — the fp arm skips snap_double because the result +// grid is double-exact by construction. +using D = bound<{{-8, 8}, notch<1, 16384>}, round_nearest | real>; + +extern "C" double bnd_perf_fp_add(D a, D b) +{ + return (a + b).raw(); +} + +// Compound subtraction fast path: `-=` on same-notch integer-backed grids +// must subtract raws directly (index-raw Q8.8 here — the case that would +// otherwise fall into the full binary-add + cross-grid-assign engine via +// `+= (-rhs)`). +using Q = bound<{{0, 255}, notch<1, 256>}, snap>; + +extern "C" long bnd_perf_sub_compound(long a, long b) +{ + Q x = Q::from_raw(static_cast(a)); + x -= Q::from_raw(static_cast(b)); + return static_cast(x.raw()); +} + +// Range decode fast path: iterating a grid must stay call-free integer code — +// both the value-storage arm (integer grid) and the index-storage arm +// (fractional grid) of bound_range::iterator::operator*. A call means the +// decode fell back into the rational/assignment engine. +extern "C" long bnd_perf_range_sum() +{ + long sum = 0; + for (auto whole : bound_range<{0, 999}>{}) + sum += static_cast(whole.raw()); + for (auto fract : bound_range<{{0, 4}, notch<1, 256>}>{}) + sum += static_cast(fract.raw()); + return sum; +} diff --git a/tests/test_compound_assign.cpp b/tests/test_compound_assign.cpp index 5afbdb2..791a5d2 100644 --- a/tests/test_compound_assign.cpp +++ b/tests/test_compound_assign.cpp @@ -103,3 +103,97 @@ TEST_CASE("increment / decrement", "[bound][compound][inc]") STATIC_REQUIRE([]{ u10 a{5}; auto r = a--; return r.as(); }() == 5); // NOLINTEND(bugprone-inc-dec-in-conditions) } + +//--------------------------------------------------------------------------- +// operator-= raw fast path (perf): with equal notches `-=` subtracts raws +// directly (index rhs debits a compile-time Lower/Notch bias) instead of +// delegating to `+= (-rhs)`. These cases pin value equality with the binary +// route and the policy tail at both raw edges. [perf-paths] +//--------------------------------------------------------------------------- +TEST_CASE("compound -=: raw fast path agrees with binary subtraction", + "[bound][compound][perf-paths]") +{ + // Q8.8 — index storage both sides, bias 0. + using q88 = bound<{{0, 255}, notch<1, 256>}, snap>; + for (int whole : {0, 1, 100, 255}) + for (int sub : {0, 1, 55, 100}) + { + if (whole - sub < 0) continue; + q88 lhs{whole}; + lhs -= q88{sub}; + REQUIRE(rational{lhs} == rational{whole - sub}); + } + + // fractional raws on the same grid + q88 frac_lhs{rational{771, 256}}; // 3 + 3/256 + frac_lhs -= q88{rational{515, 256}}; // 2 + 3/256 + REQUIRE(rational{frac_lhs} == rational{1}); + + // offset index grid (negative Lower): bias = Lower/Notch = -8. + using off = bound<{{-2, 2}, notch<1, 4>}, snap>; + off offset_lhs{rational{3, 4}}; + offset_lhs -= off{rational{-1, 2}}; // 3/4 − (−1/2) = 5/4 + REQUIRE(rational{offset_lhs} == rational{5, 4}); + + // mixed storages on the same unit notch: [1000,2000] stores an unsigned + // index (Lower-relative raw), [-3000,3000] stores the value directly. + using hi = bound<{1000, 2000}, snap>; + using lo = bound<{0, 500}, snap>; + using wide = bound<{-3000, 3000}, snap>; + hi index_lhs{1500}; // index-raw lhs, value-raw rhs + index_lhs -= lo{300}; + REQUIRE(rational{index_lhs} == rational{1200}); + wide value_lhs{1500}; // value-raw lhs, index-raw rhs: + value_lhs -= hi{1200}; // bias = Lower/Notch = 1000 + REQUIRE(rational{value_lhs} == rational{300}); +} + +TEST_CASE("compound -=: policy tail at the raw edges", "[bound][compound][perf-paths]") +{ + using q_clamp = bound<{{0, 255}, notch<1, 256>}, clamp | snap>; + q_clamp clamped{1}; + clamped -= q_clamp{100}; // 1 − 100 < 0 -> clamps to Lower + REQUIRE(rational{clamped} == rational{0}); + + using q_wrap = bound<{{0, 3}, notch<1, 4>}, wrap | snap>; + q_wrap wrapped{0}; + wrapped -= q_wrap{rational{1, 4}}; // 0 − 1/4 wraps to Upper − ... = 3 + REQUIRE(rational{wrapped} == rational{3}); + + using q_sent = bound<{{0, 255}, notch<1, 256>}, sentinel | snap>; + q_sent sent{1}; + sent -= q_sent{100}; + REQUIRE(sent.is_sentinel()); + + using q_checked = bound<{{0, 255}, notch<1, 256>}, checked>; + q_checked reported{1}; + REQUIRE_THROWS_AS(([&]{ reported -= q_checked{100}; }()), bnd::bound_error); + + // in-range edge stays exact under every policy + q_clamp exact_edge{255}; + exact_edge -= q_clamp{255}; + REQUIRE(rational{exact_edge} == rational{0}); +} + +TEST_CASE("compound -=: non-fast storages still route through += (-rhs)", + "[bound][compound][perf-paths]") +{ + // rational raw falls back and stays exact + using ex = bound<{{0, 4}, notch<1, 3>}, exact | round_nearest>; + ex exact_lhs{rational{7, 3}}; + exact_lhs -= ex{rational{2, 3}}; + REQUIRE(rational{exact_lhs} == rational{5, 3}); + + // f64-backed falls back (fp raws are excluded from the raw fast path) + using rl = bound<{{-4, 4}, notch<1, 256>}, real | round_nearest>; + rl real_lhs{rational{3, 2}}; + real_lhs -= rl{rational{1, 4}}; + REQUIRE(rational{real_lhs} == rational{5, 4}); + + // cross-notch operands take the binary route + using tenths = bound<{{0, 10}, notch<1, 10>}, round_nearest>; + using quarters = bound<{{0, 10}, notch<1, 4>}, round_nearest>; + tenths cross{rational{5, 2}}; // 2.5 on the 1/10 grid + cross -= quarters{rational{1, 2}}; // 2.0 — exact on both grids + REQUIRE(rational{cross} == rational{2}); +} diff --git a/tests/test_cross_grid.cpp b/tests/test_cross_grid.cpp index c8591b7..a4ede09 100644 --- a/tests/test_cross_grid.cpp +++ b/tests/test_cross_grid.cpp @@ -13,6 +13,7 @@ #include +#include #include using namespace bnd; @@ -167,3 +168,87 @@ TEST_CASE("tier-3 integer fast paths stay engaged (and fp stays excluded)", STATIC_REQUIRE(rational{signed_whole{-7} + eighths{-0.625_b}} == rational{umax{61}, imax{-8}}); } + +TEST_CASE("regression: cross-grid assign onto rational storage keeps the value", + "[cross][assign][regression][exact]") +{ + // The boundable-rhs store used the notch-index machinery for rational-raw + // targets: an index-raw source had its VALUE rounded to a whole number + // (7/3 -> 2/1) and a rational-raw source had the grid transform applied to + // a raw that already was the value (5/3 -> corrupted). Both must store the + // exact source value. + using exact_t = bound<{{0, 4}, notch<1, 3>}, exact | round_nearest>; + + using index_src = bound<{{0, 4}, notch<1, 3>}, round_nearest>; + exact_t from_index; + from_index = index_src{rational{7, 3}}; + REQUIRE(from_index.raw() == rational{7, 3}); + + using exact_wide = bound<{{-4, 4}, notch<1, 3>}, exact | round_nearest>; + exact_t from_exact; + from_exact = exact_wide{rational{5, 3}}; + REQUIRE(from_exact.raw() == rational{5, 3}); + + using value_src = bound<{0, 4}, snap>; + exact_t from_value_raw; + from_value_raw = value_src{3}; + REQUIRE(from_value_raw.raw() == rational{3}); + + using real_src = bound<{{0, 4}, notch<1, 256>}, real | round_nearest>; + using exact_dyadic = bound<{{0, 4}, notch<1, 256>}, exact | round_nearest>; + exact_dyadic from_real; + from_real = real_src{rational{513, 256}}; + REQUIRE(from_real.raw() == rational{513, 256}); + + // rounding still happens when the source is off the target grid + using exact_coarse = bound<{{0, 4}, 1}, exact | round_nearest>; + exact_coarse rounded; + rounded = index_src{rational{7, 3}}; // 2.33 -> 2 on the unit grid + REQUIRE(rounded.raw() == rational{2}); +} + +TEST_CASE("scalar comparison integer arm agrees with the rational decode", + "[cross][compare][perf-paths]") +{ + // Q-format (index raw) vs integral scalar takes the cross-multiplied + // integer arm; every verdict must match the exact rational comparison, + // including at A's numeric_limits extremes. + using q88 = bound<{{0, 255}, notch<1, 256>}, round_nearest>; + STATIC_REQUIRE(scalar_index_cmp_fits); + + auto agree = [](auto probe, auto scalar) { + rational exact_lhs = as_rational(probe); + rational exact_rhs{scalar}; + REQUIRE((probe == scalar) == (exact_lhs == exact_rhs)); + REQUIRE((probe < scalar) == (exact_lhs < exact_rhs)); + REQUIRE((probe > scalar) == (exact_lhs > exact_rhs)); + }; + for (int scalar : {std::numeric_limits::min(), -1, 0, 41, 42, 43, 255, + std::numeric_limits::max()}) + { + agree(q88{42}, scalar); + agree(q88{rational{10753, 256}}, scalar); // 42 + 1/256: != 42, > 42 + agree(q88{rational{10751, 256}}, scalar); // 42 - 1/256: != 42, < 42 + agree(q88{0}, scalar); + agree(q88{255}, scalar); + } + + // offset index grid (negative Lower -> nonzero bias) + using offset_q = bound<{{-8, 8}, notch<1, 16384>}, round_nearest>; + STATIC_REQUIRE(scalar_index_cmp_fits); + for (int scalar : {std::numeric_limits::min(), -9, -8, -1, 0, 1, 8, + std::numeric_limits::max()}) + { + agree(offset_q{-8}, scalar); + agree(offset_q{rational{-1, 16384}}, scalar); + agree(offset_q{0}, scalar); + agree(offset_q{8}, scalar); + } + + // 64-bit scalars whose cross term c·d can overflow imax are excluded and + // fall back to the exact rational path — still correct. + STATIC_REQUIRE(!scalar_index_cmp_fits); + REQUIRE(q88{42} < std::numeric_limits::max()); + REQUIRE(q88{42} > std::numeric_limits::min()); + REQUIRE(!(q88{42} == std::numeric_limits::max())); +} diff --git a/tests/test_range.cpp b/tests/test_range.cpp index c2cea0d..4e41e06 100644 --- a/tests/test_range.cpp +++ b/tests/test_range.cpp @@ -191,3 +191,75 @@ TEST_CASE("bound_range::strided visits every step-th value", "[range][strided]") for (auto v : f.strided(2)) fseen.push_back(rational{v}); REQUIRE(fseen == std::vector{rational{0}, rational{1, 2}, rational{1}}); } + +//--------------------------------------------------------------------------- +// storage-kind decode — operator* takes integer fast arms for index/value +// raws (see range.hpp); every arm must agree with the analytic +// Lower + index·Notch, and the ctor must invert it. [perf-paths] pins which +// arm each representative type dispatches to. +//--------------------------------------------------------------------------- +namespace +{ + template + void require_decodes_analytically() + { + using value_type = typename RangeType::value_type; + RangeType r; + std::size_t position = 0; + for (auto b : r) + { + rational expected = (Lower + + (rational{position} * Notch).value()).value(); + REQUIRE(as_rational(b) == expected); + ++position; + } + REQUIRE(position == r.size()); + } +} + +TEST_CASE("bound_range: decode agrees with Lower + i*Notch on every storage kind", + "[range][perf-paths]") +{ + require_decodes_analytically>(); // value raw + require_decodes_analytically>(); // value raw, signed + require_decodes_analytically}>>(); // index raw + require_decodes_analytically}>>(); // index raw, offset Lower + require_decodes_analytically>(); // sentinel slot excluded + require_decodes_analytically}, exact>>(); // rational raw fallback +#ifndef BND_MATH_FIXED // under BND_MATH_FIXED the real storage arm is elided + require_decodes_analytically}, real | round_nearest>>(); // fp raw fallback +#endif +} + +TEST_CASE("bound_range: start ctor inverts the decode on every storage kind", + "[range][perf-paths]") +{ + auto first_equals_start = [](auto range_tag, auto start_value) { + using RangeType = decltype(range_tag); + typename RangeType::value_type start{start_value}; + RangeType r{start}; + REQUIRE(as_rational(*r.begin()) == as_rational(start)); + }; + first_equals_start(bound_range<{0, 999}>{}, 500); + first_equals_start(bound_range<{{0, 4}, notch<1, 256>}>{}, 2); + first_equals_start(bound_range<{{0, 2}, notch<1, 3>}, exact>{}, 1); +} + +TEST_CASE("bound_range: sentinel policy never yields the sentinel slot", "[range][perf-paths]") +{ + for (auto b : bound_range<{0, 100}, sentinel>{}) + REQUIRE(!b.is_sentinel()); +} + +TEST_CASE("bound_range: fast decode arms engage (dispatch pins)", "[range][perf-paths]") +{ + // The operator* fast arms are gated on the storage kind; these pins fail if + // a storage-selection change silently reroutes a type to another arm. + STATIC_REQUIRE(value_raw::value_type>); + STATIC_REQUIRE(index_raw}>::value_type>); + STATIC_REQUIRE(index_raw}>::value_type>); + STATIC_REQUIRE(rational_raw}, exact>::value_type>); +#ifndef BND_MATH_FIXED // under BND_MATH_FIXED the real storage arm is elided + STATIC_REQUIRE(fp_raw}, real | round_nearest>::value_type>); +#endif +}