docs: add teaching examples and video project
This commit is contained in:
parent
e93bc20b0a
commit
7a06f94577
42 changed files with 8022 additions and 0 deletions
371
show/POPULAR_SCIENCE_OUTLINE.md
Normal file
371
show/POPULAR_SCIENCE_OUTLINE.md
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
# 从暴力搜索到位运算:寻找 Minecraft 史莱姆区块的优化之旅
|
||||
|
||||
这是一份视频/博客共用的叙事提纲。依据当前源码与研究记录整理,性能数字引用已有实验,没有在整理时重新测量。
|
||||
|
||||
后续演示代码已放在当前 `show/` 目录,见 [README.md](README.md);
|
||||
本次重新测量的速度见 [BENCHMARK.md](BENCHMARK.md)。下文保留构思时的研究数字与补测建议,
|
||||
制作时以新报告取值。主线线程策略对比只包含按 seed 与固定坐标后遍历 seed;展平实现移到实验目录,不列为本阶段优化。
|
||||
|
||||
**主线的测试分段已调整:**A 用 65536 seed × 257×257 对比 CPU 与按 seed 的 CUDA;
|
||||
B 用 65536 seed × 4097×4097 对比 02、03,扩大每个 seed 的地图;
|
||||
C 用固定 seed × 8193×8193 对比 03 与后续坐标优化。
|
||||
A/B、B/C 之间画测试负载分界线,分别重测 02、03,只在每组内部计算速度比。
|
||||
原来的 64 seed 极端负载用于补充说明并行度,不作为主线“249 倍优化”的依据。
|
||||
|
||||
主线:**先让相同的工作执行得更快,再利用问题的结构减少需要执行的工作。**
|
||||
|
||||
建议标题:
|
||||
|
||||
- 《为了找一片史莱姆区块,我从 CPU 写到了 CUDA,最后开始拆随机数》
|
||||
- 《从暴力枚举到解析求解:Minecraft 史莱姆区块搜索优化实录》
|
||||
|
||||
## 1. 开场:我们到底在找什么?
|
||||
|
||||
用一个 5×5 区块网格开场:想找一片全是史莱姆区块的区域。
|
||||
一个区块是否满足条件,由世界种子和区块坐标共同决定。
|
||||
把这个判断看成一个确定的函数:
|
||||
|
||||
```text
|
||||
isSlime(seed, chunkX, chunkZ) -> true / false
|
||||
```
|
||||
|
||||
如果粗略把每格看作独立的十分之一概率,一个指定的实心 5×5 图案出现概率约为 `10^-25`。
|
||||
这只是直觉估算;同一 seed 下各格共享生成机制,后面的算法恰恰要利用这些关系。
|
||||
|
||||
引出三个问题,后续每次切换任务时在画面上注明:
|
||||
|
||||
| 任务 | 固定什么 | 搜索什么 | 对应实现 |
|
||||
| --- | --- | --- | --- |
|
||||
| 原始联合暴力搜索 | seed 区间与坐标范围 | `(seed, x, z)` | 根目录 `slime.cpp`、`slime.cu` |
|
||||
| 固定 seed 找位置 | 一个世界种子 | `(x, z)` | `slime_position_cuda.cu`、`slime_position_residue_cuda.cu` |
|
||||
| 固定位置反查 seed | 图案与锚点坐标 | seed | `slime_pattern_cpp.cpp`、`slime_pattern_cuda.cu` |
|
||||
|
||||
视频不必一开始列出所有程序。先展示搜索空间像一个三维盒子:seed、X、Z。
|
||||
后续固定其中一维或两维,就是取这个盒子的一个切片。
|
||||
|
||||
贯穿演示的已知样例:世界 seed `61692151016558`,5×5 锚点 `(51069, 419124)`。
|
||||
固定这个位置,当前求解器找到了 24 个通过精确验证的低 48 位 seed。
|
||||
|
||||
## 2. 第一版:CPU 老老实实枚举
|
||||
|
||||
源码入口:[`../slime.cpp`](../slime.cpp)。
|
||||
|
||||
按“种子 → 坐标 → 图案中的每一格”解释三层循环:
|
||||
|
||||
```text
|
||||
for seed in seeds:
|
||||
for (x, z) in positions:
|
||||
if patternMatches(seed, x, z):
|
||||
record(seed, x, z)
|
||||
```
|
||||
|
||||
CPU 多线程把 seed 区间分给不同工作线程。
|
||||
判断实心图案时,只要一格失败,就能提前退出;一般不会把 25 格全部算完。
|
||||
根目录还支持“n×n 中至少 d 格满足”的条件,这里为了讲清楚后续原理,统一使用 `n=5, d=25`。
|
||||
|
||||
**画面:**几名工人各自拿一摞 seed,对每个 seed 从头扫描同一张坐标地图。
|
||||
|
||||
**转场台词:**“这些候选互不依赖,很适合并行。既然 CPU 能分给十几个线程,能不能分给 GPU 上更多的线程?”
|
||||
|
||||
## 3. 第二版:搬到 CUDA
|
||||
|
||||
源码入口:[`../slime.cu`](../slime.cu),关注 `checkSeedsKernel` 和 `checkSingleSeed`。
|
||||
|
||||
原始 CUDA 实现让一个 GPU thread 负责一个 seed,线程内部继续扫描整个坐标矩形。
|
||||
这一步改变了执行硬件,但仍然枚举相同类型的候选。
|
||||
|
||||
讲解只保留必要概念:thread 是一个工作者,block 是一组工作者,kernel 是交给 GPU 执行的函数。
|
||||
不必在科普主线中展开 CUDA API。
|
||||
|
||||
**画面:**原先几名工人变成很多工人,但每人手上的地图仍然很大。
|
||||
|
||||
现有记录中,专用的固定坐标暴力 benchmark,GPU 吞吐约为 16 线程 CPU 的 23.6 倍。
|
||||
这里必须注明:它来自 `slime_bruteforce_*_bench`,不是根目录两个程序的同任务计时。
|
||||
根目录 CPU/CUDA 的直接对比留待补测,不能把这个倍数放在根目录版本的柱子上。
|
||||
|
||||
**当前演示对照 A:**使用 `show/` 中统一语义后的 01/02,65536 个 seed、257×257 锚点、实心 5×5。
|
||||
以 CPU 搜索时间与 GPU scan 时间作硬件转换对比,完整命中列表必须一致。新报告已提供这组实测。
|
||||
|
||||
---
|
||||
|
||||
**测试负载分界:下一章保持相同坐标矩形,把 seed 数增至 65536,重新测量 02,再比较线程策略。跨横线不计算速度比。**
|
||||
|
||||
## 4. 第三版:重新分配工作,让 thread 负责坐标
|
||||
|
||||
源码入口:[`03_cuda_position.cuh`](03_cuda_position.cuh) 的 `coordinateKernel`。
|
||||
固定 seed 的研究参考实现另见 [`slime_position_cuda.cu`](../research/slime_position_cuda.cu)。
|
||||
|
||||
把坐标矩形编号,每个 thread 固定一个锚点坐标,在内部遍历 seed 并检查完整图案。
|
||||
固定 seed 时,这个循环只运行一次。
|
||||
每个 thread 先计算图案需要的 `F(x+dx)` 和 `G(z+dz)`,再将这些多项式结果传给检查函数。
|
||||
遍历 seed 时只计算两项之和与 Random,不重新计算坐标多项式。这里复用 thread 私有项,下一章才扩展为相邻 thread 共享轴项。
|
||||
图案大小通过模板展开,私有轴项无需每次动态索引;旧/新 03 的独立样本见
|
||||
[stage3_polynomial_reuse.json](results/stage3_polynomial_reuse.json),其中同时包含该编译期展开的效果。
|
||||
当前实现用 grid-stride loop,因此一个 thread 也可能在之后检查其他锚点。
|
||||
“一个 thread 一个位置”描述的是工作单元,不是永久绑定关系。
|
||||
|
||||
```text
|
||||
之前:thread(seed) -> 顺序扫描所有 (x, z)
|
||||
之后:thread(x,z) -> 顺序检查所有 seed
|
||||
```
|
||||
|
||||
好处是把长循环拆成更细的任务,使空间维度也能提供足够并行度;固定坐标项可供多个 seed 使用。
|
||||
当 seed 数很少、位置很多时,这尤其值得尝试。
|
||||
它是否更快仍受任务规模、调度成本、寄存器使用等因素影响,不能把“坐标线程化必然更快”当成定理。
|
||||
坐标线程内部仍然有提前退出,warp 分支差异也仍然存在。
|
||||
|
||||
如果希望这一章展示“更快多少”,应补一个受控对比:相同 seed 列表、相同坐标矩形、相同图案,一线程一个 seed 与一线程固定一个 `(x,z)`、内部遍历 seed 分别运行。
|
||||
对多个 seed 的总工作量都应是 `seed_count × width × height`,两边收集相同结果。
|
||||
目前已有的直接/outer-sum 对比不能证明这个线程粒度转换的独立收益。
|
||||
|
||||
**当前演示对照 B:**65536 个 seed、4097×4097 锚点,只运行 02、03。
|
||||
新版 03 已包含坐标多项式的 thread 内复用与图案大小的模板展开。5 轮 kernel 中位数:02 为 31.599 秒,03 为 17.600 秒,速度比 1.795×;不能再引用仅交换线程策略时的约 1.07×。
|
||||
03 严格按固定坐标遍历 seed,不引入展平。
|
||||
只比较同一组输入的 kernel 时间;02/03 的 GPU 完整命中列表必须相同。
|
||||
独立 CPU 核对同一 seed 列表下的 257×257 窗口,所有 GPU 命中再由独立 Java 算术验证;不把窗口验证写成完整大范围 CPU 重扫。
|
||||
原先 64 seed 的任务用于解释并行度不足,主线不使用该任务的 249 倍数字。
|
||||
|
||||
**画面:**把每个 seed 的大地图拆成小格,工作者各拿一格。
|
||||
|
||||
**转场台词:**“现在能同时检查很多位置。但邻近位置算的东西,有不少其实是重复的。”
|
||||
|
||||
---
|
||||
|
||||
**测试负载分界:接下来保持地图,固定一个 seed;重新测量 03,再与 outer sum 比较。**
|
||||
|
||||
## 5. 第四版:outer sum,把二维坐标项拆成两条轴
|
||||
|
||||
共享算术:[`slime_solver_core.hpp`](../research/slime_solver_core.hpp) 的 `coordinateXTerm`、`coordinateZTerm`。
|
||||
|
||||
史莱姆判断的坐标部分可以写成:
|
||||
|
||||
```text
|
||||
T(x,z) = F(x) + G(z)
|
||||
```
|
||||
|
||||
先展示原式,再按变量分组证明这一点。记 `J32(a)` 为 Java 有符号 int 回绕,则:
|
||||
|
||||
```text
|
||||
F(x) = J32(4987142*x*x) + J32(5947611*x)
|
||||
G(z) = 4392871*J32(z*z) + J32(389711*z)
|
||||
```
|
||||
|
||||
前两项仅依赖 X,后两项仅依赖 Z,所以拆分是精确恒等式。
|
||||
取 `X_i=F(x_i)`、`Z_j=G(z_j)`,有 `T_ij=X_i+Z_j`。
|
||||
矩阵形式是 `T = X·1_n^T + 1_m·Z^T`:把 X 列复制到每列,把 Z 行复制到每行,再逐项相加。
|
||||
动画先用 `X=(2,5)`、`Z=(10,20,30)` 展示 2×3 数值表,再引出 block 共享。
|
||||
完整 LaTeX 推导与整数语义见 [`video/OUTER_SUM.md`](video/OUTER_SUM.md)。
|
||||
|
||||
这里 F、G 必须保留 Java 原本的 int 溢出、long 转换及符号扩展规则。
|
||||
按共享核心的定义,轴项相加能用于精确判断;不能把它直接替换为无限精度下的普通二次多项式。
|
||||
|
||||
outer sum 是“每个 X 值加上每个 Z 值”,不是 outer product。
|
||||
用一列 X 数值和一行 Z 数值,动画生成二维表格,便能直观解释。
|
||||
可分离的是进入随机数生成器之前的坐标项;最终的史莱姆布尔图案没有因此直接变成可分离函数。
|
||||
|
||||
CUDA 的 `outerKernel` 用一个 block 覆盖 16×16 个锚点。
|
||||
5×5 图案需要每条轴上 `16+5-1=20` 个项,放到 shared memory,让同一个 block 复用。
|
||||
不要说“每个 tile 算 40 次就完成所有判断”:随机数运算和图案验证仍要做,节省的是重复计算的坐标项。
|
||||
|
||||
**C 组同任务实测,RTX 3060 Laptop GPU,67,125,249 个锚点,100 轮中位数:**
|
||||
|
||||
| 实现 | kernel 时间 | 结果 |
|
||||
| --- | ---: | --- |
|
||||
| 03 固定坐标,线程私有轴项 | 2.715 ms | 唯一命中已知位置 |
|
||||
| 04 tiled outer-sum,共享轴项 | 1.598 ms | 完整命中列表相同 |
|
||||
|
||||
加速约 **1.698 倍**。更多数据与命令见 [`BENCHMARK.md`](BENCHMARK.md)。
|
||||
|
||||
这个收益适合用来讲提前退出:大多数位置很快就失败,直接版本来就没有计算所有 25 格。
|
||||
重复计算减少很多,不代表总耗时也按相同比例减少。
|
||||
outer sum 仍扫描坐标面积,后面的算法才开始减少候选数量。
|
||||
|
||||
## 6. 转折:随机数其实是一个确定的位运算过程
|
||||
|
||||
源码入口:[`slime_solver_core.hpp`](../research/slime_solver_core.hpp) 的 `nextStateFromTerm`、`exactIsSlimeFromTerm`。
|
||||
|
||||
前面的 01–04 是实现层面的优化,仍然检查同一批候选。现在画出章节分界,开始探索 Random 的性质:
|
||||
状态空间有多大?已知低位能推断什么?坐标与 seed 的约束能否分别组织?哪些未知位可以直接求解?
|
||||
|
||||
这部分不用“又快了多少”的表格串联,而是按“发现一个性质 → 举例验证 → 推导新搜索策略”展开。
|
||||
详细推导见 [RANDOM_PROPERTIES.md](RANDOM_PROPERTIES.md)。XOR 合并只是把公式整理成方便分析的形式,不单列优化版本。
|
||||
|
||||
合并史莱姆公式与 Java Random 构造器中的两个 XOR,记:
|
||||
|
||||
```text
|
||||
A = 0x5DEECE66D
|
||||
B = 11
|
||||
C = 987234911 xor A
|
||||
|
||||
u = ((worldSeed + T(x,z)) xor C) mod 2^48
|
||||
v = (A*u + B) mod 2^48
|
||||
q = v >> 17 // next(31) 的首次输出
|
||||
```
|
||||
|
||||
通常 `q % 10 == 0` 就满足这一格;Java `nextInt(10)` 还有拒绝后重抽的分支,后文单独交代。
|
||||
|
||||
### 发现一:64 位世界 seed,这个判断只看低 48 位
|
||||
|
||||
更准确的说法是“**忽略第 48 位及以上的输入位,也就是 64 位中的高 16 位**”。
|
||||
不能说“忽略高 48 位”。内部状态只有 48 位,具有相同低 48 位的 world seed 对史莱姆判断等价。
|
||||
这不意味着它们生成的整个 Minecraft 世界都相同。
|
||||
|
||||
**举一反三:**可以按低 48 位给 world seed 分等价类;验证算法时,也可用相差 2^48 的输入检查等价性,而不是把它写成额外的 kernel 加速。
|
||||
|
||||
**画面:**64 个位格子,高 16 位淡出,留下 48 位。
|
||||
|
||||
### 发现二:要整除 10,至少必须是偶数
|
||||
|
||||
首次输出 `q` 的最低位,是内部状态 `v` 的第 17 位,位编号从 0 开始。
|
||||
乘法、加法、XOR 在模 `2^18` 下只依赖输入的低 18 位。
|
||||
所以不用知道全部 48 位,就可以判断“首次输出是否为偶数”。
|
||||
|
||||
对实心 5×5,要求 25 格的这个必要条件同时成立。
|
||||
已知位置枚举种子低 18 位的 `2^18=262144` 种取值,只保留 **84 种**。
|
||||
这里是首次输出模型中的必要条件,不是完整的模 10 判断;Java 拒绝重抽可能产生例外,后面的候选生成器需要明确这一覆盖边界。
|
||||
|
||||
**举一反三:**低位可以先决定部分条件,就可以从“逐个完整 seed 判断”改为“先生成符合条件的低位取值,再补上更高位”。
|
||||
|
||||
**画面:**48 位色条分出低 18 位;262144 个小点通过 25 格筛子,剩下 84 个。
|
||||
|
||||
## 7. 坐标侧分支:先查余数,再展开整个世界范围
|
||||
|
||||
为了延续前面的“固定 seed 找坐标”,先用新发现优化坐标扫描。
|
||||
源码入口:[`slime_position_residue_cuda.cu`](../research/slime_position_residue_cuda.cu)。
|
||||
|
||||
上述首次输出奇偶条件,对 X、Z 都具有 `2^18` 的坐标周期。
|
||||
先检查 `2^18 × 2^18 = 2^36` 个余数对,把通过的类展开成具体坐标,再精确验证。
|
||||
必须明确:周期属于低位必要条件,完整史莱姆判断不会因此在这些坐标间完全相同。
|
||||
|
||||
已知 seed、两轴锚点范围 `[-1875000,1874999]`:
|
||||
|
||||
```text
|
||||
14,062,500,000,000 个锚点
|
||||
-> 检查 68,719,476,736 个余数对
|
||||
-> 保留 180,476 个余数对
|
||||
-> 展开 36,937,048 个具体候选
|
||||
-> 精确验证,找到已知位置
|
||||
```
|
||||
|
||||
余数 kernel 实测 1.931 秒,精确 CPU 验证约 284 ms,连同轴表准备,全流程约 2.5 秒。
|
||||
直接扫描全范围约 429 秒是根据较大范围实测吞吐量线性外推;约 170 倍加速的分母是这个外推值。
|
||||
“候选数量减少约 381000 倍”和“总耗时改善约 170 倍”应分别展示。
|
||||
默认边界约束的是锚点,5×5 图案还会从锚点向外延伸四格。
|
||||
|
||||
**画面:**地图由重复的低位坐标类组成;先筛选类,再只点亮类中需要精查的具体位置。
|
||||
|
||||
## 8. 种子侧分支:18 + 17 + 13,解析求解剩余高位
|
||||
|
||||
现在明确切换问题:固定 `(51069,419124)`,反过来找世界 seed。
|
||||
源码入口:`slime_pattern_cpp.cpp`、`slime_pattern_cuda.cu`,共享核心的 `solveUpper13`。
|
||||
|
||||
低 18 位筛完后,中间 17 位目前仍然枚举:
|
||||
|
||||
```text
|
||||
84 种通过筛选的低 18 位取值 × 2^17 = 11,010,048 种低 35 位取值
|
||||
```
|
||||
|
||||
不要把“逐位 lifting”讲成每一位都不断淘汰候选。
|
||||
对首次输出奇偶条件,低 18 位确定后,后续高位不会改变它;这部分仍是扩展枚举。
|
||||
|
||||
最后的高 13 位有更强的结构:
|
||||
|
||||
```text
|
||||
seed = lower35 + m * 2^35, 0 <= m < 8192
|
||||
```
|
||||
|
||||
合并后的 XOR 常数小于 `2^35`。固定 lower35,改变 m,所有图案单元的首次输出状态都获得同一个高位位移。
|
||||
采用 `n = 1645*m mod 8192` 的可逆重编号后,每格的整除条件变成“某个区间里的 n 应满足哪个模 5 余数”。
|
||||
每格只有一个回绕阈值,把阈值排序,按区间求约束交集,就能获得可行高位,无需逐个试完 8192 个 m。
|
||||
候选最终仍用准确的 Java `nextInt(10)` 检查。
|
||||
|
||||
**主视频画面:**25 条长度为 8192 的候选带,阈值前后颜色变化;叠加后只剩少数共同位置。
|
||||
1645、4965 等常数的推导放博客附录,视频只讲“共同位移、回绕阈值、集合交集”。
|
||||
|
||||
**固定位置结果,RTX 3060 Laptop GPU/16 线程 CPU:**
|
||||
|
||||
| 实现 | 时间 | 口径 |
|
||||
| --- | ---: | --- |
|
||||
| 旧 CPU 暴力 | 约 33.7 小时 | 根据实测 `2^32` 个 seed 外推 `2^48` |
|
||||
| 新 CPU 求解 | 约 0.61 秒 | 实测搜索,24 个已知结果全部找回 |
|
||||
| 旧 CUDA 暴力 | 约 85.7 分钟 | 根据实测 `2^36` 个 seed 外推 `2^48` |
|
||||
| 新 CUDA 求解 | 59.6 ms | 实测 kernel |
|
||||
| 新 CUDA 全流程 | 约 0.36 秒 | 包含启动、准备、传输等 |
|
||||
|
||||
GPU 算法比较约 **86300 倍**,全流程约 **14300 倍**。
|
||||
这组数字属于固定坐标反查,不能接在坐标扫描的 1.21 倍、170 倍后相乘。
|
||||
另有 64 个位置的性能分布,应在博客中引用,避免只展示一个有利样例。
|
||||
这里的“搜索完成”限于当前首次输出候选模型,不能据此宣称穷尽含拒绝尾部分支的所有 Java seed。
|
||||
|
||||
## 9. 科研也包括试错:看起来聪明,不一定更快
|
||||
|
||||
主视频选一到两个即可,其他放博客侧栏:
|
||||
|
||||
- **FFT:**全匹配写成相关运算很诱人,但直接扫描大量候选会在第一两格失败;FFT 还要付准备、变换与内存成本。已有测试比直接扫描慢约 12.5–25.2 倍。
|
||||
- **缓存:**1024 种互不相同的低 18 位取值,始终开启缓存零命中,反而更慢;该路线已移除,详见试错报告。
|
||||
- **在十格筛选之后微调:**约 235 亿个 lower35 状态,只有 133815 个进入这个阶段,优化尾部难以改变总时间。
|
||||
- **错误的逐位剪枝:**低于 18 位时拿不同输出位的奇偶当真实必要条件,会把已知有效 seed 删掉。更高位拆分也不会自动产生新剪枝。
|
||||
|
||||
转场可以说:“不仅要看淘汰了多少,还要看为了做这个筛选,花掉了多少工作。”
|
||||
来源:[`REJECTED_EXPERIMENTS.md`](../research/REJECTED_EXPERIMENTS.md)、[`FFT_EXPERIMENT.md`](../research/FFT_EXPERIMENT.md)、[X/Z 筛轴试错报告](../research/XZ_FILTER_TRIAL_REPORT.md)。
|
||||
|
||||
## 10. 结尾:哪些问题解决了,哪些还没有?
|
||||
|
||||
当前最有成果的两条路线,是固定 seed 的坐标搜索,以及固定坐标的 seed 反查。
|
||||
X/Z 独立筛轴路线因低位生成和过早配对瓶颈停止维护,见试错报告。
|
||||
全 seed × 全世界目录仍未完成。
|
||||
|
||||
Java `nextInt(10)` 在首次输出落到 `2^31` 个可能值中的最后八个值时会拒绝并重抽。
|
||||
单次概率为 `8/2^31`。现有候选过滤与解析求解主要描述首次抽取,所以可能遗漏依赖后续抽取才成立的解。
|
||||
精确最终验证能消除误报,不能补回之前被过滤掉的候选。
|
||||
博客写清楚这个边界;视频可用一句话和字幕交代,避免称为严格穷尽。
|
||||
|
||||
结束台词建议:
|
||||
|
||||
> 一开始,我们让更多线程帮忙试。后来,我们复用了相同的计算。再后来,我们开始问:哪些候选根本不值得试?最显著的加速,来自对随机数结构的理解。
|
||||
|
||||
## 11. 制作安排与需要补齐的实验
|
||||
|
||||
### 视频节奏建议
|
||||
|
||||
第一集约 15–20 分钟:开场与 CPU(2 分钟)、CUDA 与线程任务拆分(3 分钟)、outer sum(2 分钟)、Random 与低位过滤(3 分钟)、余数坐标搜索(2 分钟)、高位解析求解(3–5 分钟)、结果与边界(1 分钟)。
|
||||
试错部分以已停止路线的报告为依据。博客可保留全部章节,用公式与源码链接补充推导。
|
||||
|
||||
每次优化都采用相同的叙述节奏:**发现重复/浪费 → 改法 → 相同任务验证 → 时间结果 → 剩余瓶颈**。
|
||||
|
||||
### 图表口径
|
||||
|
||||
使用三组独立图表,图上标出任务、硬件、规模、时间区域和“实测/外推”:
|
||||
|
||||
1. 联合暴力枚举:CPU、seed-thread CUDA、细粒度候选 CUDA。需要补测。
|
||||
2. 固定 seed 找位置:直接扫描、outer-sum、余数过滤。分别说明小矩形实测与全范围外推。
|
||||
3. 固定位置找 seed:CPU/CUDA 暴力与新求解器,kernel 和全流程分开。
|
||||
|
||||
不要把毫秒级扫描、秒级全范围搜索和反查 seed 混在一张“版本越来越快”的图里。
|
||||
跨度大的 seed 求解时间适合用对数坐标,但柱子上仍标明原始时间。
|
||||
|
||||
### 根目录基线的修正与统一
|
||||
|
||||
录制性能对照前,根目录实现存在几处需要处理的可比性问题:
|
||||
|
||||
- CPU 分线程时使用从 0 开始的区间,未加命令行 startSeed;非零 `-s` 时实际扫描区间不符。
|
||||
- CUDA kernel 没有 endSeed 边界判断,批次大小非 block 整数倍时会多查 seed。
|
||||
- CPU 记录所有满足的位置,CUDA 每个 seed 找到第一个位置就返回;计数和工作量语义不同。
|
||||
- 根目录坐标计算及 Random 运算使用有符号整数溢出写法;受控基线应采用显式无符号回绕与 Java 符号扩展,避免依赖 C++ 未定义行为。
|
||||
- 测量应统一收集命中元组,避免循环内 printf 和逐 seed 原子计数掩盖搜索本身。
|
||||
|
||||
这些是从源码读取出的基线问题,本提纲没有修改实现。
|
||||
如果想展示旧程序原貌,可保留旧版本作为历史片段;正式性能图使用明确标注的修正基线。
|
||||
|
||||
对照先做小范围命中列表一致性验证,再做预热后的多次交替计时。
|
||||
固定 seed 数、坐标面积、图案、输出方式,使用 CUDA event 测 kernel,另报全流程时间。
|
||||
线程粒度实验适合覆盖“少 seed/大地图”和“多 seed/小地图”两种任务,而不是预设一种策略总是胜出。
|
||||
|
||||
### 素材来源
|
||||
|
||||
- 当前保留方案与范围:[`OPTIMIZATION_SUMMARY.md`](../research/OPTIMIZATION_SUMMARY.md)
|
||||
- 性能、分布与复现命令:[`BENCHMARK.md`](../research/BENCHMARK.md)
|
||||
- 算术与高位求解:[`slime_solver_core.hpp`](../research/slime_solver_core.hpp)
|
||||
- 运行入口和已知样例:[`README.md`](../research/README.md)
|
||||
|
||||
正文中的数字来自这些已有记录,不应写成在此次提纲整理中重新跑出的结果。
|
||||
Loading…
Add table
Add a link
Reference in a new issue