chore: remove obsolete codex implementations
This commit is contained in:
parent
0751449021
commit
a1e43bb679
8 changed files with 0 additions and 1267 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -15,8 +15,6 @@ cmake-build-*/
|
|||
/slime
|
||||
/slime-cuda
|
||||
/sliming
|
||||
/codex/slime_cpu
|
||||
/codex/slime_cuda
|
||||
|
||||
# Python environments and caches.
|
||||
__pycache__/
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -27,7 +27,6 @@ clean:
|
|||
rm -f slime slime-cuda sliming
|
||||
$(MAKE) -C bench clean
|
||||
$(MAKE) -C research clean
|
||||
$(MAKE) -C codex clean
|
||||
$(MAKE) -C show clean
|
||||
$(MAKE) -C tools clean
|
||||
help:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ H100 编译:`make -C bench -B CUDA_ARCH=90`。需要 CUDA Toolkit 和 C++17
|
|||
| [`research/`](research/README.md) | 求解原型、算法实验、正确性验证和测量报告 |
|
||||
| [`research/experiments/`](research/experiments/README.md) | 最近的完整覆盖、成组筛选和调度实验 |
|
||||
| [`show/`](show/README.md) | 分步骤教学代码、博客/视频稿及 Manim 工程 |
|
||||
| [`codex/`](codex/README.md) | 早期 CPU/CUDA 对照实现及脚本 |
|
||||
| [`exp/`](exp/README.md) | 独立 SMT 逆向搜索探索 |
|
||||
| [`tools/`](tools/README.md) | 单区块检查小工具与历史汇编参考 |
|
||||
| `slime.cpp`、`slime.cu` | 最初的暴力扫描实现,保留原入口 |
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
CXX ?= g++
|
||||
NVCC ?= nvcc
|
||||
|
||||
CXXFLAGS ?= -O3 -std=c++17 -march=native -pthread
|
||||
NVCCFLAGS ?= -O3 -std=c++17
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: slime_cpu slime_cuda
|
||||
|
||||
slime_cpu: slime_cpu.cpp
|
||||
$(CXX) $(CXXFLAGS) -o $@ $<
|
||||
|
||||
slime_cuda: slime_cuda.cu
|
||||
$(NVCC) $(NVCCFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f slime_cpu slime_cuda slime_cpu.exe slime_cuda.exe
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# 早期 CPU/CUDA 对照实现
|
||||
|
||||
这里保留早期按种子扫描的 CPU、CUDA 实现及 WSL 对照脚本。它不是当前全有效种子范围基准 CLI 的入口。
|
||||
|
||||
- 当前 CLI:[bench/README.md](../bench/README.md)
|
||||
- 早期实现构建:`make -C codex`
|
||||
- 对照脚本参数:`bash codex/bench_wsl.sh --help`
|
||||
|
||||
目录名和源码路径保留,避免破坏既有脚本。编译输出忽略;脚本默认把临时构建和运行结果放在 `/tmp` 下。
|
||||
|
|
@ -1,310 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
BUILD_DIR="${SLIME_BENCH_BUILD_DIR:-/tmp/slime-bench-build}"
|
||||
OUT_DIR="${SLIME_BENCH_OUT_DIR:-/tmp/slime-bench-results}"
|
||||
|
||||
CXX="${CXX:-g++}"
|
||||
NVCC="${NVCC:-nvcc}"
|
||||
CXXFLAGS="${CXXFLAGS:--O3 -std=c++17 -march=native -pthread -fwrapv}"
|
||||
NVCCFLAGS="${NVCCFLAGS:--O3 -std=c++17 -Xcompiler=-fwrapv}"
|
||||
|
||||
RUNS=3
|
||||
WARMUPS=1
|
||||
DO_CPU=1
|
||||
DO_CUDA=1
|
||||
KEEP_OUTPUT=0
|
||||
SEARCH_ARGS=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash codex/bench_wsl.sh [bench options] [-- common slime args]
|
||||
|
||||
Bench options:
|
||||
--runs <n> Timed runs per binary (default: 3)
|
||||
--warmups <n> Untimed warmup runs per binary (default: 1)
|
||||
--skip-cpu Do not build/run CPU binaries
|
||||
--skip-cuda Do not build/run CUDA binaries
|
||||
--build-dir <dir> Build directory (default: /tmp/slime-bench-build)
|
||||
--out-dir <dir> Output directory (default: /tmp/slime-bench-results)
|
||||
--keep-output Keep per-run stdout/stderr logs
|
||||
--help Show this help
|
||||
|
||||
Common slime args are passed to all binaries. Use only options supported by both
|
||||
the original programs and the codex programs: -s, -e, -n, -d, -r.
|
||||
|
||||
Default common args:
|
||||
-s 0 -e 100000 -n 3 -r 60
|
||||
|
||||
Examples:
|
||||
bash codex/bench_wsl.sh
|
||||
bash codex/bench_wsl.sh --runs 5 -- -s 0 -e 1000000 -n 3 -r 100
|
||||
bash codex/bench_wsl.sh --skip-cuda -- -s 0 -e 200000 -n 4 -r 80
|
||||
|
||||
Notes:
|
||||
* CPU comparison runs codex/slime_cpu with --all-locations to match the
|
||||
original CPU program, which scans every matching location.
|
||||
* CUDA comparison runs codex/slime_cuda in first-hit-per-seed mode to match
|
||||
the original CUDA program.
|
||||
* The original CPU program ignores non-zero -s while splitting thread ranges,
|
||||
so use -s 0 for fair CPU result counts.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
need_value() {
|
||||
local name="$1"
|
||||
local value="${2:-}"
|
||||
[[ -n "$value" ]] || die "$name needs a value"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--runs)
|
||||
need_value "$1" "${2:-}"
|
||||
RUNS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--warmups)
|
||||
need_value "$1" "${2:-}"
|
||||
WARMUPS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-cpu)
|
||||
DO_CPU=0
|
||||
shift
|
||||
;;
|
||||
--skip-cuda)
|
||||
DO_CUDA=0
|
||||
shift
|
||||
;;
|
||||
--build-dir)
|
||||
need_value "$1" "${2:-}"
|
||||
BUILD_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--out-dir)
|
||||
need_value "$1" "${2:-}"
|
||||
OUT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--keep-output)
|
||||
KEEP_OUTPUT=1
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
SEARCH_ARGS=("$@")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
SEARCH_ARGS+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ "$RUNS" =~ ^[0-9]+$ && "$RUNS" -gt 0 ]] || die "--runs must be a positive integer"
|
||||
[[ "$WARMUPS" =~ ^[0-9]+$ ]] || die "--warmups must be a non-negative integer"
|
||||
|
||||
if [[ "${#SEARCH_ARGS[@]}" -eq 0 ]]; then
|
||||
SEARCH_ARGS=(-s 0 -e 10000000 -n 3 -r 60)
|
||||
fi
|
||||
|
||||
START_SEED_ARG=0
|
||||
for ((i = 0; i < ${#SEARCH_ARGS[@]}; ++i)); do
|
||||
if [[ "${SEARCH_ARGS[$i]}" == "-s" && $((i + 1)) -lt ${#SEARCH_ARGS[@]} ]]; then
|
||||
START_SEED_ARG="${SEARCH_ARGS[$((i + 1))]}"
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$BUILD_DIR" "$OUT_DIR"
|
||||
|
||||
read -r -a CXXFLAGS_ARR <<< "$CXXFLAGS"
|
||||
read -r -a NVCCFLAGS_ARR <<< "$NVCCFLAGS"
|
||||
|
||||
build_cpu() {
|
||||
command -v "$CXX" >/dev/null 2>&1 || die "cannot find C++ compiler '$CXX'"
|
||||
|
||||
echo "Building CPU binaries with: $CXX $CXXFLAGS"
|
||||
"$CXX" "${CXXFLAGS_ARR[@]}" "$ROOT_DIR/slime.cpp" -o "$BUILD_DIR/user_cpu"
|
||||
"$CXX" "${CXXFLAGS_ARR[@]}" "$ROOT_DIR/codex/slime_cpu.cpp" -o "$BUILD_DIR/codex_cpu"
|
||||
}
|
||||
|
||||
build_cuda() {
|
||||
if ! command -v "$NVCC" >/dev/null 2>&1; then
|
||||
echo "Skipping CUDA: cannot find '$NVCC'" >&2
|
||||
DO_CUDA=0
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Building CUDA binaries with: $NVCC $NVCCFLAGS"
|
||||
"$NVCC" "${NVCCFLAGS_ARR[@]}" "$ROOT_DIR/slime.cu" -o "$BUILD_DIR/user_cuda"
|
||||
"$NVCC" "${NVCCFLAGS_ARR[@]}" "$ROOT_DIR/codex/slime_cuda.cu" -o "$BUILD_DIR/codex_cuda"
|
||||
}
|
||||
|
||||
extract_count() {
|
||||
local file="$1"
|
||||
local count
|
||||
count="$(sed -n 's/.*hits=\([0-9][0-9]*\).*/\1/p' "$file" | tail -n 1)"
|
||||
if [[ -z "$count" ]]; then
|
||||
count="$(sed -n 's/.*found \([0-9][0-9]*\) seeds.*/\1/p' "$file" | tail -n 1)"
|
||||
fi
|
||||
if [[ -z "$count" ]]; then
|
||||
count="$(grep -Ec '^(hit|found seed)' "$file" || true)"
|
||||
fi
|
||||
echo "${count:-unknown}"
|
||||
}
|
||||
|
||||
run_once() {
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
local stdout_file="$OUT_DIR/$name.stdout"
|
||||
local stderr_file="$OUT_DIR/$name.stderr"
|
||||
local time_file="$OUT_DIR/$name.time"
|
||||
|
||||
set +e
|
||||
/usr/bin/time -f '%e' -o "$time_file" "$@" >"$stdout_file" 2>"$stderr_file"
|
||||
local status=$?
|
||||
set -e
|
||||
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
echo "Command failed: $*" >&2
|
||||
echo "--- stderr ---" >&2
|
||||
tail -n 80 "$stderr_file" >&2 || true
|
||||
exit "$status"
|
||||
fi
|
||||
|
||||
local elapsed
|
||||
elapsed="$(tr -d '\r\n' < "$time_file")"
|
||||
local count
|
||||
count="$(extract_count "$stdout_file")"
|
||||
echo "$elapsed $count"
|
||||
}
|
||||
|
||||
mean() {
|
||||
awk '
|
||||
NF { sum += $1; n += 1 }
|
||||
END {
|
||||
if (n == 0) print "nan";
|
||||
else printf "%.6f", sum / n;
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
speedup() {
|
||||
local base="$1"
|
||||
local candidate="$2"
|
||||
awk -v base="$base" -v candidate="$candidate" '
|
||||
BEGIN {
|
||||
if (candidate > 0) printf "%.2fx", base / candidate;
|
||||
else printf "n/a";
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
bench_binary() {
|
||||
local label="$1"
|
||||
shift
|
||||
local -a command=("$@")
|
||||
|
||||
echo
|
||||
echo "== $label =="
|
||||
echo "Command: ${command[*]}"
|
||||
|
||||
for ((i = 1; i <= WARMUPS; ++i)); do
|
||||
echo "Warmup $i/$WARMUPS..."
|
||||
run_once "${label// /_}.warmup.$i" "${command[@]}" >/dev/null
|
||||
done
|
||||
|
||||
local times_file="$OUT_DIR/${label// /_}.times"
|
||||
: > "$times_file"
|
||||
local last_count="unknown"
|
||||
for ((i = 1; i <= RUNS; ++i)); do
|
||||
local result elapsed count
|
||||
result="$(run_once "${label// /_}.run.$i" "${command[@]}")"
|
||||
elapsed="${result%% *}"
|
||||
count="${result#* }"
|
||||
last_count="$count"
|
||||
echo "$elapsed" >> "$times_file"
|
||||
printf 'Run %d/%d: %ss, reported=%s\n' "$i" "$RUNS" "$elapsed" "$count"
|
||||
done
|
||||
|
||||
local avg
|
||||
avg="$(mean < "$times_file")"
|
||||
printf '%s\t%s\t%s\n' "$label" "$avg" "$last_count" >> "$OUT_DIR/summary.tsv"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo
|
||||
echo "Summary"
|
||||
printf '%-16s %12s %12s %12s\n' "binary" "avg_s" "reported" "speedup"
|
||||
|
||||
local user_cpu_avg="" codex_cpu_avg="" user_cuda_avg="" codex_cuda_avg=""
|
||||
while IFS=$'\t' read -r label avg count; do
|
||||
case "$label" in
|
||||
user_cpu) user_cpu_avg="$avg" ;;
|
||||
codex_cpu) codex_cpu_avg="$avg" ;;
|
||||
user_cuda) user_cuda_avg="$avg" ;;
|
||||
codex_cuda) codex_cuda_avg="$avg" ;;
|
||||
esac
|
||||
done < "$OUT_DIR/summary.tsv"
|
||||
|
||||
while IFS=$'\t' read -r label avg count; do
|
||||
local base="" sp="baseline"
|
||||
case "$label" in
|
||||
codex_cpu) base="$user_cpu_avg" ;;
|
||||
codex_cuda) base="$user_cuda_avg" ;;
|
||||
esac
|
||||
if [[ -n "$base" ]]; then
|
||||
sp="$(speedup "$base" "$avg")"
|
||||
fi
|
||||
printf '%-16s %12s %12s %12s\n' "$label" "$avg" "$count" "$sp"
|
||||
done < "$OUT_DIR/summary.tsv"
|
||||
}
|
||||
|
||||
echo "Root: $ROOT_DIR"
|
||||
echo "Build dir: $BUILD_DIR"
|
||||
echo "Output dir: $OUT_DIR"
|
||||
echo "Args: ${SEARCH_ARGS[*]}"
|
||||
echo "Runs: $RUNS timed, $WARMUPS warmup"
|
||||
|
||||
if [[ "$DO_CPU" -eq 1 && "$START_SEED_ARG" != "0" ]]; then
|
||||
echo "Warning: original slime.cpp ignores non-zero -s in its CPU thread split; CPU counts will not be apples-to-apples." >&2
|
||||
fi
|
||||
|
||||
: > "$OUT_DIR/summary.tsv"
|
||||
|
||||
if [[ "$DO_CPU" -eq 1 ]]; then
|
||||
build_cpu
|
||||
bench_binary user_cpu "$BUILD_DIR/user_cpu" "${SEARCH_ARGS[@]}"
|
||||
bench_binary codex_cpu "$BUILD_DIR/codex_cpu" "${SEARCH_ARGS[@]}" --all-locations
|
||||
fi
|
||||
|
||||
if [[ "$DO_CUDA" -eq 1 ]]; then
|
||||
build_cuda
|
||||
if [[ "$DO_CUDA" -eq 1 ]]; then
|
||||
bench_binary user_cuda "$BUILD_DIR/user_cuda" "${SEARCH_ARGS[@]}"
|
||||
bench_binary codex_cuda "$BUILD_DIR/codex_cuda" "${SEARCH_ARGS[@]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if [[ "$KEEP_OUTPUT" -eq 0 ]]; then
|
||||
find "$OUT_DIR" -type f \( -name '*.stdout' -o -name '*.stderr' -o -name '*.time' -o -name '*.times' \) -delete
|
||||
else
|
||||
echo
|
||||
echo "Kept logs in $OUT_DIR"
|
||||
fi
|
||||
|
|
@ -1,403 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL;
|
||||
constexpr uint64_t kJavaAddend = 0xBULL;
|
||||
constexpr uint64_t kJavaMask = (1ULL << 48) - 1;
|
||||
constexpr uint64_t kSlimeXor = 987234911ULL;
|
||||
|
||||
struct Args {
|
||||
int64_t startSeed = 0;
|
||||
int64_t endSeed = 1'000'000;
|
||||
int32_t n = 3;
|
||||
int32_t required = -1;
|
||||
int32_t radius = 100;
|
||||
int32_t threads = std::max(1u, std::thread::hardware_concurrency());
|
||||
int64_t seedBatch = 256;
|
||||
int32_t maxResults = 32;
|
||||
bool allLocations = false;
|
||||
};
|
||||
|
||||
struct Hit {
|
||||
int64_t seed;
|
||||
int32_t x;
|
||||
int32_t z;
|
||||
int32_t count;
|
||||
};
|
||||
|
||||
[[noreturn]] void usage(int exitCode = 1) {
|
||||
std::cerr
|
||||
<< "Usage: slime_cpu [args...]\n"
|
||||
<< " -s <startSeed> First world seed, inclusive (default: 0)\n"
|
||||
<< " -e <endSeed> Last world seed, exclusive (default: 1000000)\n"
|
||||
<< " -n <chunkSize> N for the NxN window (default: 3)\n"
|
||||
<< " -d <numChunks> Required slime chunks in the window (default: N*N)\n"
|
||||
<< " -r <radius> Search start positions from -r..r on both axes (default: 100)\n"
|
||||
<< " -p <threads> CPU worker threads (default: hardware concurrency)\n"
|
||||
<< " --batch <seeds> Seeds fetched per worker batch (default: 256)\n"
|
||||
<< " --max-results <n> Print at most n hits, while still counting all hits (default: 32)\n"
|
||||
<< " --all-locations Count every matching location, not just the first per seed\n"
|
||||
<< " --help Show this help\n";
|
||||
std::exit(exitCode);
|
||||
}
|
||||
|
||||
const char* requireValue(int argc, char** argv, int& i) {
|
||||
if (i + 1 >= argc) {
|
||||
usage();
|
||||
}
|
||||
return argv[++i];
|
||||
}
|
||||
|
||||
Args parseArgs(int argc, char** argv) {
|
||||
Args args;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--help") {
|
||||
usage(0);
|
||||
} else if (arg == "-s") {
|
||||
args.startSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-e") {
|
||||
args.endSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-n") {
|
||||
args.n = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-d") {
|
||||
args.required = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-r") {
|
||||
args.radius = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-p") {
|
||||
args.threads = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--batch") {
|
||||
args.seedBatch = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "--max-results") {
|
||||
args.maxResults = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--all-locations") {
|
||||
args.allLocations = true;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t cells = int64_t(args.n) * args.n;
|
||||
if (args.required < 0) {
|
||||
args.required = static_cast<int32_t>(cells);
|
||||
}
|
||||
if (args.startSeed >= args.endSeed || args.n <= 0 || args.radius < 0 ||
|
||||
args.threads <= 0 || args.seedBatch <= 0 || args.maxResults < 0 ||
|
||||
cells > INT32_MAX || args.required <= 0 || args.required > cells) {
|
||||
usage();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
uint64_t javaIntAsLongBits(uint32_t value) {
|
||||
return (value & 0x80000000u) ? (0xFFFFFFFF00000000ULL | value) : uint64_t(value);
|
||||
}
|
||||
|
||||
uint64_t coordTerm(int32_t chunkX, int32_t chunkZ) {
|
||||
const uint32_t x = static_cast<uint32_t>(chunkX);
|
||||
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
||||
|
||||
uint64_t term = 0;
|
||||
term += javaIntAsLongBits(x * x * 4987142u);
|
||||
term += javaIntAsLongBits(x * 5947611u);
|
||||
term += javaIntAsLongBits(z * z) * 4392871ULL;
|
||||
term += javaIntAsLongBits(z * 389711u);
|
||||
return term;
|
||||
}
|
||||
|
||||
bool isSlimeFromTerm(uint64_t worldSeedBits, uint64_t term) {
|
||||
uint64_t randomSeed = ((worldSeedBits + term) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask;
|
||||
|
||||
for (;;) {
|
||||
randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask;
|
||||
const uint32_t bits = static_cast<uint32_t>(randomSeed >> 17);
|
||||
const uint32_t value = bits % 10u;
|
||||
if (bits - value + 9u <= 0x7FFFFFFFu) {
|
||||
return value == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int trailingZeroes(uint64_t value) {
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long index = 0;
|
||||
_BitScanForward64(&index, value);
|
||||
return static_cast<int>(index);
|
||||
#else
|
||||
return __builtin_ctzll(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t lowBitsMask(int32_t bits) {
|
||||
const int32_t rem = bits & 63;
|
||||
return rem == 0 ? ~0ULL : ((1ULL << rem) - 1ULL);
|
||||
}
|
||||
|
||||
struct SearchSpace {
|
||||
int32_t n;
|
||||
int32_t radius;
|
||||
int32_t window;
|
||||
int32_t chunkWidth;
|
||||
int32_t chunkRows;
|
||||
int32_t chunkWords;
|
||||
int32_t windowWords;
|
||||
uint64_t lastWindowMask;
|
||||
std::vector<uint64_t> terms;
|
||||
|
||||
SearchSpace(int32_t radiusIn, int32_t nIn)
|
||||
: n(nIn),
|
||||
radius(radiusIn),
|
||||
window(radiusIn * 2 + 1),
|
||||
chunkWidth(window + nIn - 1),
|
||||
chunkRows(window + nIn - 1),
|
||||
chunkWords((chunkWidth + 63) / 64),
|
||||
windowWords((window + 63) / 64),
|
||||
lastWindowMask(lowBitsMask(window)),
|
||||
terms(size_t(chunkWidth) * chunkRows) {
|
||||
for (int32_t z = 0; z < chunkRows; ++z) {
|
||||
for (int32_t x = 0; x < chunkWidth; ++x) {
|
||||
terms[size_t(z) * chunkWidth + x] = coordTerm(x - radius, z - radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Workspace {
|
||||
std::vector<uint64_t> rows;
|
||||
std::vector<uint64_t> horizontal;
|
||||
std::vector<uint8_t> rowValues;
|
||||
std::vector<int32_t> rowRing;
|
||||
std::vector<int32_t> vertical;
|
||||
|
||||
explicit Workspace(const SearchSpace& space)
|
||||
: rows(size_t(space.chunkRows) * space.chunkWords),
|
||||
horizontal(size_t(space.chunkRows) * space.chunkWords),
|
||||
rowValues(space.chunkWidth),
|
||||
rowRing(size_t(space.n) * space.window),
|
||||
vertical(space.window) {}
|
||||
};
|
||||
|
||||
struct ResultSink {
|
||||
std::mutex outputMutex;
|
||||
std::atomic<uint64_t> hits{0};
|
||||
int32_t maxResults;
|
||||
|
||||
explicit ResultSink(int32_t maxResultsIn) : maxResults(maxResultsIn) {}
|
||||
|
||||
void record(const Hit& hit) {
|
||||
const uint64_t index = hits.fetch_add(1, std::memory_order_relaxed);
|
||||
if (index >= static_cast<uint64_t>(maxResults)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(outputMutex);
|
||||
std::cout << "hit seed=" << hit.seed
|
||||
<< " x=" << hit.x
|
||||
<< " z=" << hit.z
|
||||
<< " count=" << hit.count << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
uint64_t shiftedWord(const uint64_t* row, int32_t words, int32_t word, int32_t shift) {
|
||||
const int32_t whole = shift >> 6;
|
||||
const int32_t bits = shift & 63;
|
||||
const int32_t src = word + whole;
|
||||
const uint64_t lo = src < words ? row[src] : 0ULL;
|
||||
if (bits == 0) {
|
||||
return lo;
|
||||
}
|
||||
const uint64_t hi = src + 1 < words ? row[src + 1] : 0ULL;
|
||||
return (lo >> bits) | (hi << (64 - bits));
|
||||
}
|
||||
|
||||
uint64_t findFullSquare(
|
||||
int64_t seed,
|
||||
const SearchSpace& space,
|
||||
Workspace& workspace,
|
||||
ResultSink& sink,
|
||||
bool allLocations
|
||||
) {
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
std::fill(workspace.rows.begin(), workspace.rows.end(), 0ULL);
|
||||
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords;
|
||||
const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth;
|
||||
for (int32_t x = 0; x < space.chunkWidth; ++x) {
|
||||
if (isSlimeFromTerm(seedBits, termRow[x])) {
|
||||
row[x >> 6] |= 1ULL << (x & 63);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
const uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords;
|
||||
uint64_t* out = workspace.horizontal.data() + size_t(z) * space.chunkWords;
|
||||
for (int32_t word = 0; word < space.chunkWords; ++word) {
|
||||
uint64_t mask = row[word];
|
||||
for (int32_t shift = 1; shift < space.n; ++shift) {
|
||||
mask &= shiftedWord(row, space.chunkWords, word, shift);
|
||||
}
|
||||
out[word] = mask;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t found = 0;
|
||||
for (int32_t z = 0; z < space.window; ++z) {
|
||||
for (int32_t word = 0; word < space.windowWords; ++word) {
|
||||
uint64_t mask = ~0ULL;
|
||||
for (int32_t dz = 0; dz < space.n; ++dz) {
|
||||
mask &= workspace.horizontal[(size_t(z + dz) * space.chunkWords) + word];
|
||||
}
|
||||
if (word == space.windowWords - 1) {
|
||||
mask &= space.lastWindowMask;
|
||||
}
|
||||
|
||||
while (mask != 0) {
|
||||
const int32_t bit = trailingZeroes(mask);
|
||||
const int32_t x = word * 64 + bit;
|
||||
sink.record({seed, x - space.radius, z - space.radius, space.n * space.n});
|
||||
++found;
|
||||
if (!allLocations) {
|
||||
return found;
|
||||
}
|
||||
mask &= mask - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint64_t findAtLeastCount(
|
||||
int64_t seed,
|
||||
int32_t required,
|
||||
const SearchSpace& space,
|
||||
Workspace& workspace,
|
||||
ResultSink& sink,
|
||||
bool allLocations
|
||||
) {
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
std::fill(workspace.vertical.begin(), workspace.vertical.end(), 0);
|
||||
|
||||
uint64_t found = 0;
|
||||
for (int32_t z = 0; z < space.chunkRows; ++z) {
|
||||
const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth;
|
||||
for (int32_t x = 0; x < space.chunkWidth; ++x) {
|
||||
workspace.rowValues[x] = isSlimeFromTerm(seedBits, termRow[x]) ? 1 : 0;
|
||||
}
|
||||
|
||||
int32_t* ringRow = workspace.rowRing.data() + size_t(z % space.n) * space.window;
|
||||
if (z >= space.n) {
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
workspace.vertical[x] -= ringRow[x];
|
||||
}
|
||||
}
|
||||
|
||||
int32_t rowSum = 0;
|
||||
for (int32_t x = 0; x < space.n; ++x) {
|
||||
rowSum += workspace.rowValues[x];
|
||||
}
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
if (x != 0) {
|
||||
rowSum += workspace.rowValues[x + space.n - 1];
|
||||
rowSum -= workspace.rowValues[x - 1];
|
||||
}
|
||||
ringRow[x] = rowSum;
|
||||
workspace.vertical[x] += rowSum;
|
||||
}
|
||||
|
||||
if (z + 1 < space.n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32_t startZ = z - space.n + 1;
|
||||
for (int32_t x = 0; x < space.window; ++x) {
|
||||
const int32_t count = workspace.vertical[x];
|
||||
if (count >= required) {
|
||||
sink.record({seed, x - space.radius, startZ - space.radius, count});
|
||||
++found;
|
||||
if (!allLocations) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void worker(
|
||||
const Args& args,
|
||||
const SearchSpace& space,
|
||||
std::atomic<int64_t>& nextSeed,
|
||||
ResultSink& sink
|
||||
) {
|
||||
Workspace workspace(space);
|
||||
for (;;) {
|
||||
const int64_t begin = nextSeed.fetch_add(args.seedBatch, std::memory_order_relaxed);
|
||||
if (begin >= args.endSeed) {
|
||||
return;
|
||||
}
|
||||
const int64_t end = std::min(args.endSeed, begin + args.seedBatch);
|
||||
for (int64_t seed = begin; seed < end; ++seed) {
|
||||
if (args.required == args.n * args.n) {
|
||||
findFullSquare(seed, space, workspace, sink, args.allLocations);
|
||||
} else {
|
||||
findAtLeastCount(seed, args.required, space, workspace, sink, args.allLocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const Args args = parseArgs(argc, argv);
|
||||
const SearchSpace space(args.radius, args.n);
|
||||
ResultSink sink(args.maxResults);
|
||||
std::atomic<int64_t> nextSeed(args.startSeed);
|
||||
|
||||
std::cout << "CPU search: seeds [" << args.startSeed << ", " << args.endSeed
|
||||
<< "), N=" << args.n << ", required=" << args.required
|
||||
<< ", radius=" << args.radius << ", threads=" << args.threads
|
||||
<< (args.allLocations ? ", all locations\n" : ", first location per seed\n");
|
||||
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(args.threads);
|
||||
for (int32_t i = 0; i < args.threads; ++i) {
|
||||
workers.emplace_back(worker, std::cref(args), std::cref(space), std::ref(nextSeed), std::ref(sink));
|
||||
}
|
||||
for (std::thread& thread : workers) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
const auto finished = std::chrono::steady_clock::now();
|
||||
const std::chrono::duration<double> elapsed = finished - started;
|
||||
std::cout << "checked " << (args.endSeed - args.startSeed)
|
||||
<< " seeds, hits=" << sink.hits.load(std::memory_order_relaxed)
|
||||
<< ", elapsed=" << elapsed.count() << "s\n";
|
||||
return 0;
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "error: " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,523 +0,0 @@
|
|||
#include <cuda_runtime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL;
|
||||
constexpr uint64_t kJavaAddend = 0xBULL;
|
||||
constexpr uint64_t kJavaMask = (1ULL << 48) - 1;
|
||||
constexpr uint64_t kSlimeXor = 987234911ULL;
|
||||
constexpr int32_t kTileW = 16;
|
||||
constexpr int32_t kTileH = 16;
|
||||
constexpr int32_t kMaskFastPathMaxN = 49;
|
||||
|
||||
struct Args {
|
||||
int64_t startSeed = 0;
|
||||
int64_t endSeed = 1'000'000;
|
||||
int32_t n = 3;
|
||||
int32_t required = -1;
|
||||
int32_t radius = 100;
|
||||
int64_t seedBatch = 0;
|
||||
int32_t maxResults = 32;
|
||||
bool allLocations = false;
|
||||
};
|
||||
|
||||
struct Hit {
|
||||
int64_t seed;
|
||||
int32_t x;
|
||||
int32_t z;
|
||||
int32_t count;
|
||||
};
|
||||
|
||||
void checkCuda(cudaError_t error, const char* expr, const char* file, int line) {
|
||||
if (error == cudaSuccess) {
|
||||
return;
|
||||
}
|
||||
std::cerr << file << ':' << line << ": CUDA call failed: " << expr
|
||||
<< ": " << cudaGetErrorString(error) << '\n';
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
#define CUDA_CHECK(expr) checkCuda((expr), #expr, __FILE__, __LINE__)
|
||||
|
||||
[[noreturn]] void usage(int exitCode = 1) {
|
||||
std::cerr
|
||||
<< "Usage: slime_cuda [args...]\n"
|
||||
<< " -s <startSeed> First world seed, inclusive (default: 0)\n"
|
||||
<< " -e <endSeed> Last world seed, exclusive (default: 1000000)\n"
|
||||
<< " -n <chunkSize> N for the NxN window (default: 3)\n"
|
||||
<< " -d <numChunks> Required slime chunks in the window (default: N*N)\n"
|
||||
<< " -r <radius> Search start positions from -r..r on both axes (default: 100)\n"
|
||||
<< " --batch <seeds> Seeds per kernel launch (default: max(total/100, 1000000))\n"
|
||||
<< " --max-results <n> Print at most n hits, while still counting all hits (default: 32)\n"
|
||||
<< " --all-locations Count every matching location, not just the first per seed\n"
|
||||
<< " --help Show this help\n";
|
||||
std::exit(exitCode);
|
||||
}
|
||||
|
||||
const char* requireValue(int argc, char** argv, int& i) {
|
||||
if (i + 1 >= argc) {
|
||||
usage();
|
||||
}
|
||||
return argv[++i];
|
||||
}
|
||||
|
||||
Args parseArgs(int argc, char** argv) {
|
||||
Args args;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--help") {
|
||||
usage(0);
|
||||
} else if (arg == "-s") {
|
||||
args.startSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-e") {
|
||||
args.endSeed = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "-n") {
|
||||
args.n = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-d") {
|
||||
args.required = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "-r") {
|
||||
args.radius = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--batch") {
|
||||
args.seedBatch = std::stoll(requireValue(argc, argv, i));
|
||||
} else if (arg == "--max-results") {
|
||||
args.maxResults = std::stoi(requireValue(argc, argv, i));
|
||||
} else if (arg == "--all-locations") {
|
||||
args.allLocations = true;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t cells = int64_t(args.n) * args.n;
|
||||
if (args.required < 0) {
|
||||
args.required = static_cast<int32_t>(cells);
|
||||
}
|
||||
if (args.startSeed >= args.endSeed || args.n <= 0 || args.radius < 0 ||
|
||||
args.seedBatch < 0 || args.maxResults < 0 || cells > INT32_MAX ||
|
||||
args.required <= 0 || args.required > cells) {
|
||||
usage();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
__host__ __device__ __forceinline__ uint64_t javaIntAsLongBits(uint32_t value) {
|
||||
return static_cast<uint64_t>(static_cast<int64_t>(static_cast<int32_t>(value)));
|
||||
}
|
||||
|
||||
__host__ __device__ __forceinline__ uint64_t coordXTerm(int32_t chunkX) {
|
||||
const uint32_t x = static_cast<uint32_t>(chunkX);
|
||||
return javaIntAsLongBits(x * x * 4987142u) + javaIntAsLongBits(x * 5947611u);
|
||||
}
|
||||
|
||||
__host__ __device__ __forceinline__ uint64_t coordZTerm(int32_t chunkZ) {
|
||||
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
||||
return javaIntAsLongBits(z * z) * 4392871ULL + javaIntAsLongBits(z * 389711u);
|
||||
}
|
||||
|
||||
__host__ __device__ __forceinline__ uint64_t coordTerm(int32_t chunkX, int32_t chunkZ) {
|
||||
return coordXTerm(chunkX) + coordZTerm(chunkZ);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ bool isSlimeFromTerm(uint64_t worldSeedBits, uint64_t term) {
|
||||
uint64_t randomSeed = ((worldSeedBits + term) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask;
|
||||
|
||||
for (;;) {
|
||||
randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask;
|
||||
const uint32_t bits = static_cast<uint32_t>(randomSeed >> 17);
|
||||
const uint32_t value = bits % 10u;
|
||||
if (bits - value + 9u <= 0x7FFFFFFFu) {
|
||||
return value == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ bool isNextInt10ZeroFast(uint32_t bits) {
|
||||
const uint32_t product = bits * 0xCCCCCCCDu;
|
||||
const uint32_t rotated = (product >> 1) | (product << 31);
|
||||
return rotated <= 429496729u;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ bool isSlimeFromSplitTerms(uint64_t worldSeedBits, uint64_t xTerm, uint64_t zTerm) {
|
||||
uint64_t randomSeed = ((worldSeedBits + xTerm + zTerm) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask;
|
||||
randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask;
|
||||
return isNextInt10ZeroFast(static_cast<uint32_t>(randomSeed >> 17));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ bool isSlimeChunkInline(uint64_t worldSeedBits, int32_t chunkX, int32_t chunkZ) {
|
||||
return isSlimeFromSplitTerms(worldSeedBits, coordXTerm(chunkX), coordZTerm(chunkZ));
|
||||
}
|
||||
|
||||
std::vector<uint64_t> makeCoordTerms(int32_t radius, int32_t n) {
|
||||
const int32_t window = radius * 2 + 1;
|
||||
const int32_t chunkWidth = window + n - 1;
|
||||
std::vector<uint64_t> terms(size_t(chunkWidth) * chunkWidth);
|
||||
for (int32_t z = 0; z < chunkWidth; ++z) {
|
||||
for (int32_t x = 0; x < chunkWidth; ++x) {
|
||||
terms[size_t(z) * chunkWidth + x] = coordTerm(x - radius, z - radius);
|
||||
}
|
||||
}
|
||||
return terms;
|
||||
}
|
||||
|
||||
__device__ void recordHit(
|
||||
int32_t onePerSeed,
|
||||
int32_t* seedFound,
|
||||
int64_t seedOffset,
|
||||
unsigned long long* totalHits,
|
||||
Hit* hits,
|
||||
int32_t maxHits,
|
||||
int64_t seed,
|
||||
int32_t x,
|
||||
int32_t z,
|
||||
int32_t count
|
||||
) {
|
||||
if (onePerSeed != 0) {
|
||||
if (atomicCAS(seedFound + seedOffset, 0, 1) != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned long long index = atomicAdd(totalHits, 1ULL);
|
||||
if (index < static_cast<unsigned long long>(maxHits)) {
|
||||
hits[index] = {seed, x, z, count};
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void searchTilesKernel(
|
||||
int64_t startSeed,
|
||||
int64_t seedCount,
|
||||
const uint64_t* terms,
|
||||
int32_t chunkWidth,
|
||||
int32_t window,
|
||||
int32_t n,
|
||||
int32_t required,
|
||||
int32_t radius,
|
||||
int32_t tilesPerSeed,
|
||||
int32_t tilesX,
|
||||
int32_t onePerSeed,
|
||||
int32_t* seedFound,
|
||||
unsigned long long* totalHits,
|
||||
Hit* hits,
|
||||
int32_t maxHits
|
||||
) {
|
||||
extern __shared__ uint8_t tile[];
|
||||
__shared__ unsigned long long horizontalRows[kTileH + kMaskFastPathMaxN - 1];
|
||||
|
||||
const int64_t linearBlock = blockIdx.x;
|
||||
const int64_t seedOffset = linearBlock / tilesPerSeed;
|
||||
if (seedOffset >= seedCount) {
|
||||
return;
|
||||
}
|
||||
if (onePerSeed != 0 && seedFound[seedOffset] != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t tileId = static_cast<int32_t>(linearBlock - seedOffset * tilesPerSeed);
|
||||
const int32_t tileBaseX = (tileId % tilesX) * kTileW;
|
||||
const int32_t tileBaseZ = (tileId / tilesX) * kTileH;
|
||||
const int32_t actualW = (window - tileBaseX) < kTileW ? (window - tileBaseX) : kTileW;
|
||||
const int32_t actualH = (window - tileBaseZ) < kTileH ? (window - tileBaseZ) : kTileH;
|
||||
const int32_t sharedW = actualW + n - 1;
|
||||
const int32_t sharedH = actualH + n - 1;
|
||||
const int32_t sharedCells = sharedW * sharedH;
|
||||
const int64_t seed = startSeed + seedOffset;
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
|
||||
const int32_t localThread = threadIdx.y * blockDim.x + threadIdx.x;
|
||||
const int32_t threadCount = blockDim.x * blockDim.y;
|
||||
for (int32_t index = localThread; index < sharedCells; index += threadCount) {
|
||||
const int32_t localX = index % sharedW;
|
||||
const int32_t localZ = index / sharedW;
|
||||
const size_t termIndex = size_t(tileBaseZ + localZ) * chunkWidth + tileBaseX + localX;
|
||||
tile[index] = isSlimeFromTerm(seedBits, terms[termIndex]) ? 1 : 0;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if (required == n * n && n <= kMaskFastPathMaxN) {
|
||||
for (int32_t row = localThread; row < sharedH; row += threadCount) {
|
||||
unsigned long long rowMask = 0;
|
||||
for (int32_t x = 0; x < sharedW; ++x) {
|
||||
rowMask |= static_cast<unsigned long long>(tile[row * sharedW + x]) << x;
|
||||
}
|
||||
unsigned long long mask = rowMask;
|
||||
for (int32_t shift = 1; shift < n; ++shift) {
|
||||
mask &= rowMask >> shift;
|
||||
}
|
||||
horizontalRows[row] = mask;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
const int32_t localX = threadIdx.x;
|
||||
const int32_t localZ = threadIdx.y;
|
||||
if (localX < actualW && localZ < actualH) {
|
||||
unsigned long long mask = ~0ULL;
|
||||
for (int32_t dz = 0; dz < n; ++dz) {
|
||||
mask &= horizontalRows[localZ + dz];
|
||||
}
|
||||
if (((mask >> localX) & 1ULL) != 0) {
|
||||
recordHit(
|
||||
onePerSeed,
|
||||
seedFound,
|
||||
seedOffset,
|
||||
totalHits,
|
||||
hits,
|
||||
maxHits,
|
||||
seed,
|
||||
tileBaseX + localX - radius,
|
||||
tileBaseZ + localZ - radius,
|
||||
n * n
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t localX = threadIdx.x;
|
||||
const int32_t localZ = threadIdx.y;
|
||||
if (localX >= actualW || localZ >= actualH) {
|
||||
return;
|
||||
}
|
||||
if (onePerSeed != 0 && seedFound[seedOffset] != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t count = 0;
|
||||
int32_t remaining = n * n;
|
||||
for (int32_t dz = 0; dz < n; ++dz) {
|
||||
const int32_t rowBase = (localZ + dz) * sharedW + localX;
|
||||
for (int32_t dx = 0; dx < n; ++dx) {
|
||||
count += tile[rowBase + dx];
|
||||
--remaining;
|
||||
if (count + remaining < required) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= required) {
|
||||
recordHit(
|
||||
onePerSeed,
|
||||
seedFound,
|
||||
seedOffset,
|
||||
totalHits,
|
||||
hits,
|
||||
maxHits,
|
||||
seed,
|
||||
tileBaseX + localX - radius,
|
||||
tileBaseZ + localZ - radius,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void searchSeedsFullKernel(
|
||||
int64_t startSeed,
|
||||
int64_t seedCount,
|
||||
int32_t window,
|
||||
int32_t n,
|
||||
int32_t radius,
|
||||
unsigned long long* totalHits,
|
||||
Hit* hits,
|
||||
int32_t maxHits
|
||||
) {
|
||||
const int64_t seedOffset = int64_t(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||
if (seedOffset >= seedCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t seed = startSeed + seedOffset;
|
||||
const uint64_t seedBits = static_cast<uint64_t>(seed);
|
||||
|
||||
for (int32_t x = 0; x < window; ++x) {
|
||||
const int32_t chunkX = x - radius;
|
||||
const uint64_t firstXTerm = coordXTerm(chunkX);
|
||||
for (int32_t z = 0; z < window; ++z) {
|
||||
const int32_t chunkZ = z - radius;
|
||||
if (!isSlimeFromSplitTerms(seedBits, firstXTerm, coordZTerm(chunkZ))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found = true;
|
||||
for (int32_t dx = 0; dx < n && found; ++dx) {
|
||||
const uint64_t xTerm = dx == 0 ? firstXTerm : coordXTerm(chunkX + dx);
|
||||
const int32_t firstDz = dx == 0 ? 1 : 0;
|
||||
for (int32_t dz = firstDz; dz < n; ++dz) {
|
||||
if (!isSlimeFromSplitTerms(seedBits, xTerm, coordZTerm(chunkZ + dz))) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
const unsigned long long index = atomicAdd(totalHits, 1ULL);
|
||||
if (index < static_cast<unsigned long long>(maxHits)) {
|
||||
hits[index] = {seed, chunkX, chunkZ, n * n};
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void printHits(const std::vector<Hit>& hits) {
|
||||
for (const Hit& hit : hits) {
|
||||
std::cout << "hit seed=" << hit.seed
|
||||
<< " x=" << hit.x
|
||||
<< " z=" << hit.z
|
||||
<< " count=" << hit.count << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const Args args = parseArgs(argc, argv);
|
||||
const int32_t window = args.radius * 2 + 1;
|
||||
const int32_t chunkWidth = window + args.n - 1;
|
||||
const int32_t tilesX = (window + kTileW - 1) / kTileW;
|
||||
const int32_t tilesZ = (window + kTileH - 1) / kTileH;
|
||||
const int32_t tilesPerSeed = tilesX * tilesZ;
|
||||
const size_t dynamicSharedBytes = size_t(kTileW + args.n - 1) * (kTileH + args.n - 1);
|
||||
const size_t staticSharedBytes = size_t(kTileH + kMaskFastPathMaxN - 1) * sizeof(unsigned long long);
|
||||
const bool useSeedKernel = !args.allLocations && args.required == args.n * args.n;
|
||||
const int32_t seedThreadsPerBlock = 64;
|
||||
|
||||
int device = 0;
|
||||
cudaDeviceProp prop{};
|
||||
CUDA_CHECK(cudaGetDevice(&device));
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&prop, device));
|
||||
if (!useSeedKernel && dynamicSharedBytes + staticSharedBytes > static_cast<size_t>(prop.sharedMemPerBlock)) {
|
||||
throw std::runtime_error("requested N needs more shared memory per block than this GPU allows");
|
||||
}
|
||||
|
||||
uint64_t* deviceTerms = nullptr;
|
||||
if (!useSeedKernel) {
|
||||
std::vector<uint64_t> hostTerms = makeCoordTerms(args.radius, args.n);
|
||||
CUDA_CHECK(cudaMalloc(&deviceTerms, hostTerms.size() * sizeof(uint64_t)));
|
||||
CUDA_CHECK(cudaMemcpy(deviceTerms, hostTerms.data(), hostTerms.size() * sizeof(uint64_t), cudaMemcpyHostToDevice));
|
||||
}
|
||||
|
||||
const int64_t totalSeeds = args.endSeed - args.startSeed;
|
||||
const int64_t requestedBatch = args.seedBatch > 0
|
||||
? args.seedBatch
|
||||
: std::max(totalSeeds / 100, int64_t(1'000'000));
|
||||
const int64_t maxSeedsPerLaunch = useSeedKernel
|
||||
? int64_t(INT_MAX) * seedThreadsPerBlock
|
||||
: std::max<int64_t>(1, INT_MAX / std::max<int32_t>(1, tilesPerSeed));
|
||||
const int64_t maxBatchSeeds = std::min({requestedBatch, maxSeedsPerLaunch, totalSeeds});
|
||||
|
||||
int32_t* deviceSeedFound = nullptr;
|
||||
if (!args.allLocations && !useSeedKernel) {
|
||||
CUDA_CHECK(cudaMalloc(&deviceSeedFound, size_t(maxBatchSeeds) * sizeof(int32_t)));
|
||||
}
|
||||
|
||||
unsigned long long* deviceHitCount = nullptr;
|
||||
CUDA_CHECK(cudaMalloc(&deviceHitCount, sizeof(unsigned long long)));
|
||||
|
||||
Hit* deviceHits = nullptr;
|
||||
if (args.maxResults > 0) {
|
||||
CUDA_CHECK(cudaMalloc(&deviceHits, size_t(args.maxResults) * sizeof(Hit)));
|
||||
}
|
||||
|
||||
std::cout << "CUDA search on " << prop.name << ": seeds [" << args.startSeed << ", "
|
||||
<< args.endSeed << "), N=" << args.n << ", required=" << args.required
|
||||
<< ", radius=" << args.radius << ", tile=" << kTileW << "x" << kTileH
|
||||
<< ", mode=" << (useSeedKernel ? "seed-early-exit" : "tile-shared")
|
||||
<< (args.allLocations ? ", all locations\n" : ", first location per seed\n");
|
||||
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
uint64_t totalHits = 0;
|
||||
int32_t printed = 0;
|
||||
|
||||
for (int64_t seed = args.startSeed; seed < args.endSeed;) {
|
||||
const int64_t remainingSeeds = args.endSeed - seed;
|
||||
const int64_t batchSeeds = std::min(maxBatchSeeds, remainingSeeds);
|
||||
const int32_t remainingPrints = std::max<int32_t>(0, args.maxResults - printed);
|
||||
|
||||
CUDA_CHECK(cudaMemset(deviceHitCount, 0, sizeof(unsigned long long)));
|
||||
if (!args.allLocations && !useSeedKernel) {
|
||||
CUDA_CHECK(cudaMemset(deviceSeedFound, 0, size_t(batchSeeds) * sizeof(int32_t)));
|
||||
}
|
||||
|
||||
if (useSeedKernel) {
|
||||
const int64_t blocks = (batchSeeds + seedThreadsPerBlock - 1) / seedThreadsPerBlock;
|
||||
searchSeedsFullKernel<<<static_cast<unsigned int>(blocks), seedThreadsPerBlock>>>(
|
||||
seed,
|
||||
batchSeeds,
|
||||
window,
|
||||
args.n,
|
||||
args.radius,
|
||||
deviceHitCount,
|
||||
deviceHits,
|
||||
remainingPrints
|
||||
);
|
||||
} else {
|
||||
const int64_t blocks = batchSeeds * tilesPerSeed;
|
||||
searchTilesKernel<<<static_cast<unsigned int>(blocks), dim3(kTileW, kTileH), dynamicSharedBytes>>>(
|
||||
seed,
|
||||
batchSeeds,
|
||||
deviceTerms,
|
||||
chunkWidth,
|
||||
window,
|
||||
args.n,
|
||||
args.required,
|
||||
args.radius,
|
||||
tilesPerSeed,
|
||||
tilesX,
|
||||
args.allLocations ? 0 : 1,
|
||||
deviceSeedFound,
|
||||
deviceHitCount,
|
||||
deviceHits,
|
||||
remainingPrints
|
||||
);
|
||||
}
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
CUDA_CHECK(cudaDeviceSynchronize());
|
||||
|
||||
unsigned long long batchHits = 0;
|
||||
CUDA_CHECK(cudaMemcpy(&batchHits, deviceHitCount, sizeof(batchHits), cudaMemcpyDeviceToHost));
|
||||
totalHits += batchHits;
|
||||
|
||||
const int32_t copyHits = static_cast<int32_t>(
|
||||
std::min<unsigned long long>(batchHits, static_cast<unsigned long long>(remainingPrints))
|
||||
);
|
||||
if (copyHits > 0) {
|
||||
std::vector<Hit> hostHits(copyHits);
|
||||
CUDA_CHECK(cudaMemcpy(hostHits.data(), deviceHits, size_t(copyHits) * sizeof(Hit), cudaMemcpyDeviceToHost));
|
||||
printHits(hostHits);
|
||||
printed += copyHits;
|
||||
}
|
||||
|
||||
seed += batchSeeds;
|
||||
std::cerr << "\rchecked " << (seed - args.startSeed) << "/" << totalSeeds << " seeds" << std::flush;
|
||||
}
|
||||
std::cerr << '\n';
|
||||
|
||||
const auto finished = std::chrono::steady_clock::now();
|
||||
const std::chrono::duration<double> elapsed = finished - started;
|
||||
std::cout << "checked " << totalSeeds << " seeds, hits=" << totalHits
|
||||
<< ", elapsed=" << elapsed.count() << "s\n";
|
||||
|
||||
CUDA_CHECK(cudaFree(deviceHits));
|
||||
CUDA_CHECK(cudaFree(deviceHitCount));
|
||||
CUDA_CHECK(cudaFree(deviceSeedFound));
|
||||
if (deviceTerms != nullptr) {
|
||||
CUDA_CHECK(cudaFree(deviceTerms));
|
||||
}
|
||||
return 0;
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "error: " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue