slime/research/BENCHMARK.md

239 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Fixed-position benchmark
Date: 2026-10-03
Hardware used for this run:
- GPU: NVIDIA GeForce RTX 3060 Laptop GPU, compute capability 8.6
- CPU: the current WSL host, using 16 worker threads
- CUDA toolkit: 12.0
## Workload
Find every lower-48 seed producing a solid 5x5 slime pattern whose top-left
chunk is `(51069, 419124)`. The known answer contains 24 seeds.
The new solver performs the complete workload and checks its final candidates
with Java-compatible `nextInt(10)`. Running a complete old-style `2^48` scan is
unnecessarily expensive, so the brute-force baselines scan large contiguous
ranges, report throughput, and linearly project the full-space time.
## Results
| Implementation | Measured work | Measured time | Projected/complete time | Speedup |
|---|---:|---:|---:|---:|
| Old brute-force CPU | `2^32` seeds | 1.851 s | 121,290 s (33.7 h) | 1x |
| New solver CPU | complete, 24/24 results | 0.61 s wall | 0.61 s | ~199,000x |
| Old brute-force CUDA | `2^36` seeds | 1.256 s | 5,145 s (85.7 min) | 1x |
| New solver CUDA | complete, 24/24 results | 59.6 ms kernel | 59.6 ms | ~86,300x |
| New solver CUDA, end-to-end | complete, 24/24 results | 0.36 s wall | 0.36 s | ~14,300x |
The end-to-end CUDA row includes process startup, CUDA context creation, CPU
enumeration of the 84 lower-18 roots, allocation, copies, kernel execution, and
readback. The kernel-only comparison isolates the algorithms on the GPU.
The new CUDA kernel is about 10.2x faster than the new 16-thread CPU solver for
this particular 5x5 position. The old brute-force CUDA baseline is about 23.6x
faster than its CPU counterpart.
## Commands
```bash
make -C research
# New complete CPU solver
/usr/bin/time -f 'wall_seconds=%e' \
research/slime_pattern_cpp search \
--x 51069 --z 419124 -n 5 -p 16 --max-results 0
# Old-style CPU throughput
research/slime_bruteforce_cpp_bench \
--x 51069 --z 419124 -n 5 --count 4294967296 -p 16
# New complete CUDA solver
research/slime_pattern_cuda \
--x 51069 --z 419124 -n 5 --threads 256 --max-results 0
# Old-style CUDA throughput
research/slime_bruteforce_cuda_bench \
--x 51069 --z 419124 -n 5 --count 68719476736
```
## Cross-check against the original root CUDA executable
The repository's original `slime-cuda` was also run directly over one billion
seeds with `-n 5 -d 25 -r 1`. It took 0.439 seconds. That program checks nine
top-left positions per seed, so it is not the same fixed-position workload.
Normalized as position/seed pairs it processed about 20.5 billion pairs/s,
whereas the dedicated old-style benchmark processed about 54.7 billion
seeds/s. Using the faster dedicated baseline therefore makes the reported
new-vs-old acceleration conservative.
## Interpretation
Most of the acceleration is algorithmic, not CUDA-specific:
- brute force visits all `2^48 = 281,474,976,710,656` seed values;
- the known position has only 84 viable lower-18 roots;
- those expand to 11,010,048 lower-35-bit values;
- each low-bit value's remaining 13 bits are solved by threshold/residue intersection,
rather than enumerating another 8192 values.
Results depend strongly on pattern selectivity. Sparse patterns and patterns
with few required-slime cells retain more roots and can produce many more upper
solutions, reducing the speedup. These numbers should not be extrapolated to
world-border position enumeration, which remains a separate problem.
## Expanded 64-position run
The initial result above is useful for correctness, but one known hit is not a
performance distribution. A second, deterministic stratified run therefore
used 64 positions:
- 32 positions at and around the known difficult coordinate, deliberately
retaining a range of nonzero lower-18 root counts;
- 32 positions covering the origin, four world-border corners, large signed
coordinates, and pseudorandom world-border positions (seed `20261004`).
For every position the new CPU and CUDA solvers completed the whole lower-48
search. The old CPU baseline actually checked `2^28` seeds per position and
the old CUDA baseline checked `2^34` per position. Aggregate measured old-style
work was therefore:
```text
CPU: 64 * 2^28 = 17,179,869,184 seeds
CUDA: 64 * 2^34 = 1,099,511,627,776 seeds (2^40)
```
CPU and CUDA agreed on the result count for all 64 positions. Only the known
coordinate had matches (24); every other sampled position had zero.
### Baseline stability
Across the 64 coordinates, brute-force throughput was:
| Baseline | Mean | Median | Range | 95% CI of mean |
|---|---:|---:|---:|---:|
| CPU, 16 threads | 2.187B seeds/s | 2.171B | 1.665B–2.686B | 2.139B–2.235B |
| RTX 3060 CUDA | 53.81B seeds/s | 53.76B | 49.36B–58.13B | 53.46B–54.15B |
The CUDA coefficient of variation was only 2.6%, which supports linear
projection of its complete `2^48` time to roughly 87 minutes.
### Root and runtime distribution
The full set contained 42 positions with zero lower-18 roots and 22 with
nonzero roots. Zero-root positions are a real algorithmic result—the initial
`2^18` parity pass proves that their entire lower-48 space is impossible—but
they are excluded from the speedup table below to avoid inflating it.
For the 22 nonzero-root positions:
| Metric | Minimum | Median | Mean | P95 | Maximum |
|---|---:|---:|---:|---:|---:|
| lower-18 roots | 1 | 20 | 28.6 | 84 | 152 |
| lower-35-bit values | 131,072 | 2,621,440 | 3,753,425 | 11,010,048 | 19,922,944 |
| New CPU search time | 6.50 ms | 166.6 ms | 225.3 ms | 606.5 ms | 1.153 s |
| New CUDA kernel time | 1.64 ms | 14.61 ms | 20.85 ms | 60.81 ms | 107.6 ms |
| CPU algorithm speedup | 107,104x | 774,825x | 4,444,739x | 18,288,517x | 19,220,240x |
| CUDA algorithm speedup | 48,055x | 369,354x | 969,525x | 3,120,648x | 3,252,226x |
The deliberately hardest sampled position was `(51069, 419125)`: 152 roots,
19,922,944 lower-35-bit values, 1.153 seconds on CPU, and 107.6 ms on CUDA. Even
there, the conservative same-platform acceleration was 107,104x on CPU and
48,055x on CUDA.
The expanded run is reproducible with:
```bash
python3 research/benchmark_suite.py \
--samples 64 \
--cpu-threads 16 \
--cpu-old-count 268435456 \
--cuda-old-count 17179869184 \
--cuda-threads 256 \
--output /tmp/slime_benchmark_64.json
```
Times in this section intentionally compare the measured search/kernel regions.
CUDA process startup and context creation are not included; the earlier
single-position end-to-end row shows their effect. The upper solver still has
the documented eight-value Java rejection-tail completeness caveat.
## Coordinate-side outer-sum CUDA experiment
The follow-up coordinate scanner fixes world seed `61692151016558` and checks
an `8193 x 8193` rectangle containing the known 5x5 top-left position:
```bash
./research/slime_position_cuda --repeats 5
```
Each invocation runs two kernels and requires identical sorted hit lists:
- direct: one CUDA thread per position, evaluating the full coordinate
polynomial as cells are needed;
- outer-sum: one 16x16 tile per CUDA block, sharing expanded `F(x+i)` and
`G(z+j)` terms across the tile.
On the same RTX 3060 Laptop GPU, median kernel times were:
| implementation | positions | kernel time | positions/s |
|---|---:|---:|---:|
| direct | 67,125,249 | 1.608 ms | 41.75 billion |
| tiled outer-sum | 67,125,249 | 1.332 ms | 50.39 billion |
That is a 1.21x outer-sum speedup. Both paths found exactly one hit,
`(51069, 419124)`. Additional 4,004,001-position cross-checks at negative and
near-world-border coordinates agreed exactly and showed 1.30--1.31x speedups.
The direct rate is high because a random position usually fails after one or
two slime tests; it does not normally pay for all 25 cells. Consequently this
experiment measures the arithmetic/reuse benefit of outer-sum, not the hoped-for
algorithmic benefit of signature grouping. It still scans the Cartesian area
and therefore is not a feasible full-world-border method.
## Full-world fixed-seed residue filter
The next prototype uses the fact that the necessary first-output parity is
periodic in both coordinates modulo `2^18`. It checks all `2^36` residue pairs
on CUDA, expands survivors over the world border, and exactly verifies only
those expanded coordinates:
```bash
./research/slime_position_residue_cuda --seed 61692151016558
```
Results for four complete residue-space runs were:
| seed | residue kernel | survivors | expanded exact candidates | exact time | hits |
|---:|---:|---:|---:|---:|---:|
| 61692151016558 | 1.931 s | 180,476 | 36,937,048 | 284 ms | 1 |
| 0 | 1.920 s | 195,672 | 40,046,949 | 352 ms | 0 |
| 1 | 1.935 s | 175,992 | 36,010,787 | 283 ms | 0 |
| 281474976710655 | 1.944 s | 181,276 | 37,098,020 | 313 ms | 0 |
The searched world rectangle contains 14,062,500,000,000 positions. For the
known seed, the necessary filter reduces the exact workload by about 381,000x.
Axis-table preparation took 224--289 ms, making total measured runtime roughly
2.5 seconds.
For a larger direct baseline, both rectangular kernels were run five times over
`2^34 = 17,179,869,184` positions. Median direct time was 524.1 ms (32.78
billion positions/s), while tiled outer-sum took 315.9 ms (54.38 billion/s).
Linear projection over the full rectangle is about 429 seconds direct or 259
seconds tiled, versus about 2.5 seconds for the residue pipeline. Thus the new
fixed-seed method is approximately 170x faster than direct and 100x faster than
the tiled Cartesian scan. The residue run itself actually processes all
`2^36` pairs; only the Cartesian full-world numbers are projections.
Like the seed-side prototype, the necessary parity filter omits the extremely
rare branch where Java rejects its first `next(31)` output. Exact verification
eliminates false positives but cannot restore a candidate already removed by
that filter.
## Retired joint-search experiments
X/Z independent filtering and its lower35 generation experiments were removed on 2026-10-11.
Their historical measurements and limitations are consolidated in
[X/Z 筛轴试错报告](XZ_FILTER_TRIAL_REPORT.md). No full-world joint speedup was established.