slime/research/BENCHMARK.md

9.9 KiB
Raw Blame History

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

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:

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:

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:

./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:

./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 筛轴试错报告. No full-world joint speedup was established.