<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Octree on Keqi的博客</title><link>https://yekq.top/tags/octree/</link><description>Recent content in Octree on Keqi的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><managingEditor>plloningye@gmail.com (Keqi Ye)</managingEditor><webMaster>plloningye@gmail.com (Keqi Ye)</webMaster><copyright>Keqi Ye</copyright><lastBuildDate>Mon, 12 May 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://yekq.top/tags/octree/index.xml" rel="self" type="application/rss+xml"/><item><title>SPH基础(三): 树结构邻居搜索</title><link>https://yekq.top/posts/sphseries/3-tree-neighbor-search/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><author>plloningye@gmail.com (Keqi Ye)</author><guid>https://yekq.top/posts/sphseries/3-tree-neighbor-search/</guid><description>&lt;h1 id="树结构基于根据-burtscher-和-pingali-的研究">树结构（基于根据 Burtscher 和 Pingali 的研究）
&lt;/h1>&lt;p>本文将介绍广泛用于 SPH 代码和 N 体模拟中的树结构（Tree Structure）。这种数据结构主要应用于以下两个核心问题：&lt;/p>
&lt;h3 id="1-粒子领域搜索neighbor-search">1. 粒子领域搜索（Neighbor Search）
&lt;/h3>&lt;p>在 SPH（光滑粒子流体力学）模拟中，每个粒子需要在核尺度 \( h \) 范围内查找邻居粒子，以便计算密度、压强梯度、粘性等物理量。树结构能够加速邻域搜索，尤其适用于粒子分布高度非均匀的情形。&lt;/p>
&lt;h3 id="2-自引力计算self-gravity-computation">2. 自引力计算（Self-Gravity Computation）
&lt;/h3>&lt;p>在引力主导的粒子系统（如星系模拟、星体碰撞）中，粒子间存在万有引力作用。直接计算所有粒子对的引力开销为 \( \mathcal{O}(N^2) \)，不可接受。基于树的近似方法（如 Barnes-Hut 算法）可将计算复杂度降至 \( \mathcal{O}(N \log N) \)，同时保持较高精度。&lt;/p>
&lt;hr>
&lt;p>接下来的章节将分别介绍树结构在上述两个问题中的构建方法、搜索策略和性能优化。&lt;/p>
&lt;p>关于传统的 &lt;strong>链表法（Linked-List）&lt;/strong> 粒子领域搜索，请参考我另一篇博文： 👉 &lt;a class="link" href="https://yekq.top/posts/SPH_neighbor_search/" >使用链表进行 SPH 邻域搜索&lt;/a>&lt;/p>
&lt;h1 id="实现步骤">实现步骤
&lt;/h1>&lt;p>根据相关文献，每次执行自引力计算或粒子邻域搜索时，通常需要以下四个步骤：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>确定粒子空间范围&lt;/strong>&lt;br>
统计所有粒子的空间边界，获取 \( x_{\min}, x_{\max}, y_{\min}, y_{\max}, z_{\min}, z_{\max} \)，用于初始化树结构的根节点或空间划分范围。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>构建树结构&lt;/strong>&lt;br>
将粒子递归划分到空间树节点中，常用的数据结构包括八叉树（Octree）或 KD 树。每个叶子节点包含若干粒子或达到最小划分条件。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>粒子空间排序&lt;/strong>&lt;br>
对粒子进行 Morton 编码（Z-order curve）或 Hilbert 曲线编码，并按照空间位置排序，便于缓存一致性和后续并行处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>树遍历计算&lt;/strong>&lt;br>
遍历树结构：&lt;/p>
&lt;ul>
&lt;li>若执行 &lt;strong>自引力计算&lt;/strong>，使用 Barnes-Hut 近似规则判断是否聚合节点质量；&lt;/li>
&lt;li>若执行 &lt;strong>邻域搜索&lt;/strong>，在每个节点中判断与查询粒子的距离是否小于核尺度 \( h \)，从而筛选可能邻居。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="确定粒子空间范围">确定粒子空间范围
&lt;/h2>&lt;p>直接使用规约算法求最大最小值&lt;/p>
&lt;h2 id="构建树结构">构建树结构
&lt;/h2>&lt;p>由于 GPU 上无法高效实现指针式链表结构，我们使用数组来模拟树的链接关系（例如子节点指针）。假设有一个整型数组 &lt;code>child&lt;/code>，其长度远大于粒子总数 &lt;code>numParticles&lt;/code>，并初始化为 &lt;code>-1&lt;/code>，表示所有节点尚未使用。&lt;/p>
&lt;h3 id="区分叶子节点与根节点">区分叶子节点与根节点
&lt;/h3>&lt;p>在树结构中，如何区分整型数组 &lt;code>child&lt;/code> 的某一个位置存储的是 &lt;strong>叶子节点（粒子）&lt;/strong> 还是 &lt;strong>根节点&lt;/strong> 是一个关键问题。&lt;/p>
&lt;h4 id="标识方法">标识方法：
&lt;/h4>&lt;ul>
&lt;li>&lt;strong>&lt;code>-1&lt;/code>&lt;/strong>：表示该位置为空，未被占用。&lt;/li>
&lt;li>&lt;strong>&lt;code>-2&lt;/code>&lt;/strong>：表示该位置已被锁定，当前线程正在使用该位置（通常用于进行原子操作）。&lt;/li>
&lt;li>&lt;strong>&lt;code>-3&lt;/code>&lt;/strong>：表示该位置是一个节点，每个节点一定会有子树（可能是子节点，也可能是粒子，粒子的子节点一定为空：-1）。&lt;/li>
&lt;li>&lt;strong>&lt;code>0&lt;/code> 到 &lt;code>numParticles - 1&lt;/code>&lt;/strong>：表示 &lt;strong>粒子&lt;/strong>，每个位置对应一个粒子。&lt;/li>
&lt;/ul>
&lt;p>显然，如果第 &lt;code>i&lt;/code> 个位置被锁定，那么：&lt;/p>
&lt;ul>
&lt;li>在 &lt;strong>三维&lt;/strong> 情况下，&lt;code>8*i+1+0&lt;/code> 到 &lt;code>7&lt;/code>（即一个八岔树及其子树等）都被锁定；&lt;/li>
&lt;li>在 &lt;strong>二维&lt;/strong> 情况下，&lt;code>4*i+1+0&lt;/code> 到 &lt;code>3&lt;/code>（即一个四岔树及其子树等）都被锁定；&lt;/li>
&lt;li>在 &lt;strong>一维&lt;/strong> 情况下，&lt;code>i&lt;/code> 及其相邻部分也会被锁定。&lt;/li>
&lt;/ul>
&lt;p>注意，任意索引&lt;code>i&lt;/code>的子节点计算方法为：&lt;code>8*i+1+0&lt;/code>，与传统的八叉树计算方法不同，这是因为我的代码中，0节点保存的是最大的根节点信息。&lt;/p>
&lt;p>&amp;ndash; 这意味着，如果一个位置被锁定，&lt;strong>其他线程将无法访问该位置的子树或其子树的子树&lt;/strong>，确保了并行计算中节点及其相关子节点的安全。&lt;/p>
&lt;h3 id="使用原子操作同步线程">使用原子操作同步线程
&lt;/h3>&lt;p>当多个线程并发地尝试访问同一个子节点槽位时，可以使用 CUDA 提供的原子操作 &lt;code>atomicCAS&lt;/code>（Compare And Swap）来进行线程间同步。&lt;/p>
&lt;p>以下是原子操作的代码示例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">int re = atomicCAS(&amp;amp;child[p], -1, -2); // 尝试占用 child[p]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>该操作的含义是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>若 &lt;code>child[p] == -1&lt;/code>&lt;/strong>：表示该槽位尚未被占用，当前线程成功将其原子地设置为 &lt;code>-2&lt;/code>，表示“锁定中”或“准备插入”；&lt;/li>
&lt;li>&lt;strong>若 &lt;code>child[p] != -1&lt;/code>&lt;/strong>：说明该槽位已被其他线程占用或已被插入，当前线程需退出或重试；&lt;/li>
&lt;li>&lt;strong>返回值 &lt;code>re&lt;/code>&lt;/strong>：表示操作前的旧值。如果 &lt;code>re == -1&lt;/code>，则说明当前线程成功锁定了该节点。如果 &lt;code>re != -1&lt;/code> ，则 &lt;code>atomicCAS&lt;/code> 函数发现比较不成立，直接返回了旧值，也不会替换&lt;code>-2&lt;/code>而打乱&lt;code>child&lt;/code>数组内容。&lt;/li>
&lt;/ul>
&lt;p>写到这里，树结构数组&lt;code>child&lt;/code>的构建就很容易了。下面是伪代码&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;span class="lnt">108
&lt;/span>&lt;span class="lnt">109
&lt;/span>&lt;span class="lnt">110
&lt;/span>&lt;span class="lnt">111
&lt;/span>&lt;span class="lnt">112
&lt;/span>&lt;span class="lnt">113
&lt;/span>&lt;span class="lnt">114
&lt;/span>&lt;span class="lnt">115
&lt;/span>&lt;span class="lnt">116
&lt;/span>&lt;span class="lnt">117
&lt;/span>&lt;span class="lnt">118
&lt;/span>&lt;span class="lnt">119
&lt;/span>&lt;span class="lnt">120
&lt;/span>&lt;span class="lnt">121
&lt;/span>&lt;span class="lnt">122
&lt;/span>&lt;span class="lnt">123
&lt;/span>&lt;span class="lnt">124
&lt;/span>&lt;span class="lnt">125
&lt;/span>&lt;span class="lnt">126
&lt;/span>&lt;span class="lnt">127
&lt;/span>&lt;span class="lnt">128
&lt;/span>&lt;span class="lnt">129
&lt;/span>&lt;span class="lnt">130
&lt;/span>&lt;span class="lnt">131
&lt;/span>&lt;span class="lnt">132
&lt;/span>&lt;span class="lnt">133
&lt;/span>&lt;span class="lnt">134
&lt;/span>&lt;span class="lnt">135
&lt;/span>&lt;span class="lnt">136
&lt;/span>&lt;span class="lnt">137
&lt;/span>&lt;span class="lnt">138
&lt;/span>&lt;span class="lnt">139
&lt;/span>&lt;span class="lnt">140
&lt;/span>&lt;span class="lnt">141
&lt;/span>&lt;span class="lnt">142
&lt;/span>&lt;span class="lnt">143
&lt;/span>&lt;span class="lnt">144
&lt;/span>&lt;span class="lnt">145
&lt;/span>&lt;span class="lnt">146
&lt;/span>&lt;span class="lnt">147
&lt;/span>&lt;span class="lnt">148
&lt;/span>&lt;span class="lnt">149
&lt;/span>&lt;span class="lnt">150
&lt;/span>&lt;span class="lnt">151
&lt;/span>&lt;span class="lnt">152
&lt;/span>&lt;span class="lnt">153
&lt;/span>&lt;span class="lnt">154
&lt;/span>&lt;span class="lnt">155
&lt;/span>&lt;span class="lnt">156
&lt;/span>&lt;span class="lnt">157
&lt;/span>&lt;span class="lnt">158
&lt;/span>&lt;span class="lnt">159
&lt;/span>&lt;span class="lnt">160
&lt;/span>&lt;span class="lnt">161
&lt;/span>&lt;span class="lnt">162
&lt;/span>&lt;span class="lnt">163
&lt;/span>&lt;span class="lnt">164
&lt;/span>&lt;span class="lnt">165
&lt;/span>&lt;span class="lnt">166
&lt;/span>&lt;span class="lnt">167
&lt;/span>&lt;span class="lnt">168
&lt;/span>&lt;span class="lnt">169
&lt;/span>&lt;span class="lnt">170
&lt;/span>&lt;span class="lnt">171
&lt;/span>&lt;span class="lnt">172
&lt;/span>&lt;span class="lnt">173
&lt;/span>&lt;span class="lnt">174
&lt;/span>&lt;span class="lnt">175
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">#define childListIndex(nodeIdx, childNum) ((nodeIdx) * TREETYPE + 1 + (childNum))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#define EMPTY -1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#define LOCKED -2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#define TRUE 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#define FALSE 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#define NODE -3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">//RealType4 是 float4 或 double4 的别名，我这样写是为了区分单双精度计算，众所周知，消费级显卡的双精度计算能力比较差。。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">//粒子的w分量存放了质量，节点的w分量存放了节点半径（正方体的边长的一半）。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">//目前还没有添加节点质心的计算逻辑，后续会加，用于计算粒子的引力，参考：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">//A hierarchical O(N log N) force-calculation algorithm 本文发表在nature上，顶礼膜拜
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">__global__ void buildTreeKernel(SPHState *deviceP, treeData *tree)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 为避免混淆，明确区分粒子和节点的位置数组
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType4 *particlePositions = deviceP-&amp;gt;positions;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volatile RealType4 *nodePositions = tree-&amp;gt;positions;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType4 *nodeRoot = tree-&amp;gt;nodeRoot;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volatile int *childList = tree-&amp;gt;childList;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int childListLength = tree-&amp;gt;childListLength;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int numParticles = tree-&amp;gt;numParticles;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int inc = blockDim.x * gridDim.x;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int i = threadIdx.x + blockIdx.x * blockDim.x;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int k;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int childIndex, child;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int lockedIndex;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType x, y, z;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType rootRadius = nodeRoot-&amp;gt;w;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType rootX = nodeRoot-&amp;gt;x;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType rootY = nodeRoot-&amp;gt;y;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType rootZ = nodeRoot-&amp;gt;z;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 添加跟踪当前节点位置的变量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType currentX, currentY, currentZ, currentR;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int depth = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int isNewParticle = TRUE;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int currentNodeIndex;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bool isInsert;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while (i &amp;lt; numParticles)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> isInsert = false;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while (!isInsert)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> RealType4 pos = particlePositions[i];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> depth = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> x = pos.x;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> y = pos.y;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> z = pos.z;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 开始于根节点（索引0）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNodeIndex = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentX = rootX;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentY = rootY;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentZ = rootZ;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentR = rootRadius;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (x &amp;gt; currentX)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex = 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (y &amp;gt; currentY)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex += 2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (z &amp;gt; currentZ)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex += 4;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 跟随路径到叶节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNodeIndex = childListIndex(currentNodeIndex, childIndex);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentR *= 0.5;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentX += ((childIndex &amp;amp; 1) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentY += ((childIndex &amp;amp; 2) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentZ += ((childIndex &amp;amp; 4) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> child = childList[currentNodeIndex];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> depth++;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //下面这个while循环是为了寻找叶子节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> while (child == NODE)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 确定在新节点中的子节点索引
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (x &amp;gt; currentX)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex = 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (y &amp;gt; currentY)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex += 2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (z &amp;gt; currentZ)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childIndex += 4;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 跟随路径到叶节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNodeIndex = childListIndex(currentNodeIndex, childIndex);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentR *= 0.5;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentX += ((childIndex &amp;amp; 1) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentY += ((childIndex &amp;amp; 2) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentZ += ((childIndex &amp;amp; 4) ? currentR : -currentR);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> child = childList[currentNodeIndex];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> depth++;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 插入粒子到当前节点的子节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //三种情况：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //1.当前叶子节点被占用，那么重试
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //2.当前叶子节点为空，这是最简单的情况，直接插入即可
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //3.当前叶子节点被粒子占用，本线程读取old节点信息，对他们两个节点进行细分，直到他们被分属到不同的象限
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (child != LOCKED)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> lockedIndex = currentNodeIndex;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (child == atomicCAS((int *)&amp;amp;childList[lockedIndex], child, LOCKED))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (child == EMPTY)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 直接插入粒子
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childList[lockedIndex] = i;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> isInsert = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //此处处理两个节点细分的情形
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 。。。。。。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //这个循环尝试细分，直到他们俩被分开到不同的象限
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 确定已存在粒子在新节点中的位置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int childNewIndex = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (oldParPos.x &amp;gt; currentX)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childNewIndex = 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (oldParPos.y &amp;gt; currentY)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childNewIndex += 2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (oldParPos.z &amp;gt; currentZ)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childNewIndex += 4;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 确定当前粒子在新节点中的位置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> int currentNewIndex = 0;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (x &amp;gt; currentX)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNewIndex = 1;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (y &amp;gt; currentY)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNewIndex += 2;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (z &amp;gt; currentZ)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> currentNewIndex += 4;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (childNewIndex != currentNewIndex)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 两个粒子在不同子节点，可以插入
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childList[childListIndex(currentNodeIndex, childNewIndex)] = child;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childList[childListIndex(currentNodeIndex, currentNewIndex)] = i;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> isInsert = true;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> break; // 退出循环
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> else
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 仍在同一子节点，需要继续细分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 注意每次细分都会创建新节点，需要保存节点的包围盒信息 用于领域搜索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // // 写入新节点的中心和半径
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nodePositions[currentNodeIndex].x = currentX;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nodePositions[currentNodeIndex].y = currentY;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nodePositions[currentNodeIndex].z = currentZ;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> nodePositions[currentNodeIndex].w = currentR;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childList[currentNodeIndex] = NODE;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 内存同步，保证所有线程都可以看到有新的节点被写入了
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> __threadfence();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } while (true);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> // 确保所有子树的写入完成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> __threadfence();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //释放锁
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> childList[lockedIndex] = NODE;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> i += inc;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="当前实现的局限性">当前实现的局限性
&lt;/h2>&lt;p>虽然稀疏树在数据结构上直观且灵活，但在并行构建和 GPU 加速场景下，它仍然存在一些明显的局限：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>节点访问不连续&lt;/strong>：稀疏树的节点在内存中通常分散存放，导致 GPU 在并行访问时频繁出现非连续内存读取，影响带宽利用率。&lt;/li>
&lt;li>&lt;strong>显存需求高&lt;/strong>：由于稀疏树存储方式不连续，GPU 在构建和查询过程中需要额外的显存来管理指针和节点结构，这使得在大规模数据下显存压力大，很容易超过显存限制。&lt;/li>
&lt;/ul>
&lt;p>为了解决这些问题，我们提出了 &lt;strong>稠密存储的并行树构建方案&lt;/strong>。该方案将树节点连续存储在内存中，并结合优化的并行算法，使 GPU 能够高效地访问数据，从而显著提升构建速度和查询性能。同时，稠密存储方案能够更合理地利用显存，降低显存占用，提高处理大规模数据的能力。&lt;/p>
&lt;p>更多关于实现细节和性能优化的方法，可以参考：&lt;a class="link" href="../dense-tree-build/" >基于稠密存储的并行树构建&lt;/a>。&lt;/p>
&lt;p>[1] Burtscher, Martin, and Keshav Pingali. &amp;ldquo;An efficient CUDA implementation of the tree-based barnes hut n-body algorithm.&amp;rdquo; &lt;em>GPU computing Gems Emerald edition&lt;/em>. Morgan Kaufmann, 2011. 75-92.&lt;/p>
&lt;hr>
&lt;h2 id="draft-false">title: 基于稠密存储的并行树构建实现方案
description:
date: 2025-08-15
slug: dense-tree-build
categories:
- 并行计算
- 树结构
- CUDA
draft: false
&lt;/h2>&lt;h1 id="基于稠密存储的并行树构建实现方案">基于稠密存储的并行树构建实现方案
&lt;/h1>&lt;h2 id="引言">引言
&lt;/h2>&lt;p>在空间划分与邻域搜索等算法中，树形数据结构（如八叉树、四叉树、Barnes-Hut 树）是高效的加速手段。&lt;br>
构建这类树结构时，&lt;strong>存储方式&lt;/strong>是影响性能与内存效率的关键因素之一。&lt;br>
常见的存储方式有两种：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>稀疏存储（Sparse Storage）&lt;/strong>：为每个节点预留较大索引空间，按需填充，易实现，但会浪费内存。&lt;/li>
&lt;li>&lt;strong>稠密存储（Dense Storage）&lt;/strong>：节点存储在连续数组中，按照构建顺序紧密排列，节省内存，但需要额外的管理逻辑。&lt;/li>
&lt;/ul>
&lt;p>你之前的实现采用了稀疏存储，在高粒子数时内存占用明显增加。&lt;br>
本文将介绍如何基于稠密存储实现高效的树构建，并给出 CUDA 并行版本的实现思路。&lt;/p>
&lt;hr>
&lt;h2 id="稠密存储的基本思想">稠密存储的基本思想
&lt;/h2>&lt;p>稠密存储的目标是：&lt;/p>
&lt;ul>
&lt;li>节点数组紧凑存放，不留大块未使用空间&lt;/li>
&lt;li>节点索引直接映射到数组下标&lt;/li>
&lt;li>在插入新节点时，通过一个全局 &lt;strong>maxNodeIndex&lt;/strong> 递减分配新位置&lt;/li>
&lt;/ul>
&lt;p>这种方法类似“倒着分配”节点空间：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[粒子0] [粒子1] ... [粒子N-1] [内部节点M] [内部节点M-1] ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>0 ~ numParticles-1&lt;/strong> 区间存放叶子节点（粒子）&lt;/li>
&lt;li>&lt;strong>numParticles ~ maxNodeIndex-1&lt;/strong> 区间存放内部节点&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="数据结构设计">数据结构设计
&lt;/h2>&lt;p>在稠密存储中，核心数据结构通常包括：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 粒子数据
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="nc">Particles&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">double&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 坐标
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 质量
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ax&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ay&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">az&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 加速度
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">depth&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 树深度
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 树节点信息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="nc">Tree&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">volatile&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 节点中心
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">volatile&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 节点半径（或质量等）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">childList&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 存储子节点索引
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">maxNodeIndex&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 当前可分配的最大索引（递减）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>关键点&lt;/strong>：&lt;code>childList&lt;/code> 是一维数组，通过 &lt;code>childListIndex(nodeIndex, childSlot)&lt;/code> 映射到节点的第几个子节点位置。这样存储方式天然紧凑。&lt;/p>
&lt;hr>
&lt;h2 id="构建流程详解">构建流程详解
&lt;/h2>&lt;p>稠密存储的构建逻辑如下：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>初始化根节点&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>根节点索引为 &lt;code>numNodes-1&lt;/code>（数组末尾）&lt;/li>
&lt;li>保存中心坐标和半径&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>遍历粒子&lt;/strong>（并行）&lt;/p>
&lt;ul>
&lt;li>每个线程处理多个粒子，步长为 &lt;code>blockDim.x * gridDim.x&lt;/code>&lt;/li>
&lt;li>缓存粒子坐标，加速比较&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>下行查找插入位置&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>从根节点开始，判断粒子属于哪个子象限（八叉树中 0~7）&lt;/li>
&lt;li>如果子节点是内部节点，继续下行&lt;/li>
&lt;li>如果子节点是空的，直接插入粒子&lt;/li>
&lt;li>如果子节点是另一个粒子，创建新的内部节点&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>创建新内部节点&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>使用 &lt;code>atomicSub&lt;/code> 从 &lt;code>maxNodeIndex&lt;/code> 分配新的节点索引&lt;/li>
&lt;li>计算新节点的中心和半径&lt;/li>
&lt;li>将已有粒子与新粒子分别插入到不同的子槽中&lt;/li>
&lt;li>如果两者仍落在同一个槽内，继续细分&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>解锁与同步&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>通过 &lt;code>atomicCAS&lt;/code> 实现插入位置的原子锁定&lt;/li>
&lt;li>使用 &lt;code>__threadfence()&lt;/code> 确保内存可见性&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="核心代码片段">核心代码片段
&lt;/h2>&lt;p>下面是简化版的稠密存储节点分配逻辑：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">child&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">atomicCAS&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">lockedIndex&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">child&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">LOCKED&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">child&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">EMPTY&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">lockedIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">particleIndex&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 分配新节点
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">newNodeIndex&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">atomicSub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">maxNodeIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 设置新节点中心与半径
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">px&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">currentX&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">dx&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">py&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">currentY&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">dy&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pz&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">currentZ&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">dz&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pm&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mf">0.5&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 初始化子节点
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">numChildren&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">childListIndex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">EMPTY&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 将旧粒子和新粒子分别放入不同槽
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">childListIndex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">oldChildSlot&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">oldParticle&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">childListIndex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">newChildSlot&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">newParticle&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__threadfence&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">childList&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">lockedIndex&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">newNodeIndex&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="并发与同步">并发与同步
&lt;/h2>&lt;p>由于多线程同时构建树，必须保证以下两点：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>原子操作&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>atomicCAS&lt;/code>（Compare And Swap）防止多个线程同时插入同一位置&lt;/li>
&lt;li>&lt;code>atomicSub&lt;/code> 分配新节点索引&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>内存同步&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>__threadfence()&lt;/code> 确保其他线程能看到已更新的节点数据&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>注意&lt;/strong>：稠密存储的节点数组在分配时是倒着使用的，所以不会和粒子索引冲突。&lt;/p>
&lt;hr>
&lt;h2 id="性能分析与优化建议">性能分析与优化建议
&lt;/h2>&lt;p>&lt;strong>优点&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>内存占用大幅减少（只存实际存在的节点）&lt;/li>
&lt;li>索引紧凑，缓存命中率高&lt;/li>
&lt;li>遍历效率提升&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>缺点&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>节点分配依赖 &lt;code>atomicSub&lt;/code>，在极高并发下可能成为瓶颈&lt;/li>
&lt;li>实现复杂度高于稀疏存储&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>优化方向&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>批量分配节点索引，减少 &lt;code>atomicSub&lt;/code> 次数&lt;/li>
&lt;li>合并锁与节点写入步骤，减少原子操作冲突&lt;/li>
&lt;li>在共享内存中缓存部分子节点&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="总结与应用场景">总结与应用场景
&lt;/h2>&lt;p>稠密存储特别适合：&lt;/p>
&lt;ul>
&lt;li>粒子数量大、空间分布均匀的模拟（如 SPH、N-body）&lt;/li>
&lt;li>对内存占用敏感的 GPU 应用&lt;/li>
&lt;li>需要频繁重建树结构的实时计算&lt;/li>
&lt;/ul>
&lt;p>与稀疏存储相比，稠密存储在 GPU 环境下通常能获得更高的性能与更低的内存消耗，尤其是在节点数量接近粒子数量时优势明显。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>参考实现&lt;/strong>：本文的构建思路源自 CUDA 并行 Barnes-Hut 树构建的常见模式，并结合了你的原始代码进行稠密化处理。&lt;/p></description></item></channel></rss>