<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on</title><link>https://topology2333.github.io/blog/posts/</link><description>Recent content in Posts on</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 31 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://topology2333.github.io/blog/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>随机积分小记：几个概念之间的关系</title><link>https://topology2333.github.io/blog/posts/math/random-int/</link><pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/random-int/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>本文的目标是理清SDE（随机微分方程）、generator（生成元）和 Fokker-Planck 方程之间的关系，并说明 generator 或许才是&lt;strong>最本质&lt;/strong>的对象，而 SDE 只是 generator 的一种具体实现方式。&lt;/p>
&lt;hr>
&lt;h2 id="基本对象markov-过程和-semigroup">基本对象：Markov 过程和 Semigroup&lt;/h2>
&lt;p>考虑 Markov 过程 $X_t$，定义：&lt;/p>
$$P_t f(x) = E[f(X_t) \mid X_0 = x]$$
&lt;/br>
&lt;p>即：给定初值 $x$，函数 $f$ 在随机演化时间 $t$ 后的期望值。&lt;/p>
&lt;hr>
&lt;p>考察性质：&lt;/p>
&lt;p>$P_0$ 为恒等算子：&lt;/p>
$$P_0 f(x) = E[f(X_0) \mid X_0 = x] = f(x)$$
&lt;p>算子的组合性：&lt;/p>
$$
\begin{align}
P_{t+s} f(x) &amp;= E[f(X_{t+s}) \mid X_0 = x]\\
&amp;= E[E[f(X_{t+s}) \mid X_t] \mid X_0 = x]\\
&amp;= E[P_s f(X_t) \mid X_0 = x]\\
&amp;= P_t(P_s f)(x)
\end{align}
$$
&lt;p>因此，算子族 $\{P_t\}_{t\ge 0}$ 对于算子复合（composition）构成半群。&lt;br>
而 Markov 过程的无记忆性保证了算子的组合结构。&lt;/p>
&lt;hr>
&lt;h2 id="generatorsemigroup-的微分">Generator：Semigroup 的微分&lt;/h2>
&lt;p>自然地，定义 generator 为参数化的算子 $P_t$ 在 $t=0$ 处对时间参数 $t$ 的导数：&lt;/p>
$$\mathcal{L}f(x) = \lim_{t\to0} \frac{P_t f(x)-f(x)}{t}$$
&lt;/br>
&lt;p>其描述了函数在随机演化下的瞬时变化率。&lt;br>
换言之，若函数 $f$ 沿着随机过程漂移，generator 告诉我们它的期望值以多快的速度变化。至此，仍不需要 SDE。只要有 Markov 过程，就自动具有 generator。&lt;/p>
&lt;hr>
&lt;h2 id="sdegenerator-的一种实现">SDE：Generator 的一种实现&lt;/h2>
&lt;p>现在引入 SDE。考虑一个由以下随机微分方程驱动的过程：&lt;/p>
$$dX_t = a(X_t)dt + b(X_t)dW_t$$
&lt;/br>
&lt;p>其中 $a(x)$ 是漂移系数，$b(x)$ 是扩散系数，$W_t$ 是标准 Brownian 运动。&lt;/p>
&lt;p>利用 Itô 引理，可以证明这个 SDE 对应的 generator 具有特殊形式：&lt;/p>
$$\mathcal{L}f(x) = a(x)f'(x) + \frac{1}{2}b^2(x)f''(x)$$
&lt;hr>
&lt;p>&lt;strong>证明&lt;/strong>：对于光滑函数 $f$，应用 Itô 引理：&lt;/p>
$$df(X_t) = f'(X_t)dX_t + \frac{1}{2}f''(X_t)(dX_t)^2$$
&lt;/br>
&lt;p>将 $dX_t = a(X_t)dt + b(X_t)dW_t$ 代入。注意二次变分规则：&lt;/p>
&lt;p>$(dt)^2 = 0,\ dt \cdot dW_t = 0,\ (dW_t)^2 = dt$&lt;/p>
&lt;p>于是：
&lt;/p>
$$(dX_t)^2 = [a(X_t)dt + b(X_t)dW_t]^2 = b^2(X_t)dt$$
$$
\begin{aligned}
df(X_t) &amp;= f'(X_t)[a(X_t)dt + b(X_t)dW_t] + \frac{1}{2}f''(X_t)b^2(X_t)dt\\
&amp;= \left[a(X_t)f'(X_t) + \frac{1}{2}b^2(X_t)f''(X_t)\right]dt + f'(X_t)b(X_t)dW_t
\end{aligned}
$$
&lt;hr>
&lt;p>对两边求期望，由于 $dW_t$ 项的期望为零：&lt;/p>
$$E[df(X_t) \mid X_0 = x] = E\left[\left[a(X_t)f'(X_t) + \frac{1}{2}b^2(X_t)f''(X_t)\right]dt \mid X_0 = x\right]$$
&lt;/br>
&lt;p>在微小时间 $dt$ 内，$X_t$ 接近初值 $x$，所以：&lt;/p>
$$E[f(X_{t+dt}) - f(X_t) \mid X_0 = x] \approx \left[a(x)f'(x) + \frac{1}{2}b^2(x)f''(x)\right]dt$$
$$\lim_{dt \to 0} \frac{E[f(X_{dt}) \mid X_0 = x] - f(x)}{dt} = a(x)f'(x) + \frac{1}{2}b^2(x)f''(x)$$
&lt;/br>
&lt;p>根据 generator 的定义 $\mathcal{L}f(x) = \lim_{t \to 0} \frac{P_tf(x) - f(x)}{t}$，我们得到：&lt;/p>
$$\mathcal{L}f(x) = a(x)f'(x) + \frac{1}{2}b^2(x)f''(x)$$
&lt;/br>
&lt;p>这表明 SDE 的 generator 形式是由 Itô 引理唯一确定的。&lt;/p>
&lt;p>这是一个重要的反演（inversion）：给定 SDE，我们可以计算出对应的 generator；反过来，给定一个这种特殊形式的 generator，我们可以构造一个相应的 SDE。&lt;br>
理论上，generator 可以不对应任何 SDE——特别是对于跳过程、Lévy 过程或一般的 Markov 过程，它们有 generator 但没有 SDE 表示。&lt;/p>
&lt;h2 id="密度的演化fokker-planck-方程">密度的演化：Fokker-Planck 方程&lt;/h2>
&lt;p>现在考虑随机过程的概率密度 $p(x,t)$ 的演化。这涉及到 generator 的一个重要伴侣，即 &lt;strong>对偶算子 $\mathcal{L}^*$&lt;/strong>。&lt;/p>
&lt;h3 id="从-backward-方程到-forward-方程">从 Backward 方程到 Forward 方程&lt;/h3>
&lt;p>由 generator 定义出发，对任意光滑函数 $f$ 和概率密度 $p(x,t)$，有：&lt;/p>
$$\frac{d}{dt}E[f(X_t)] = E[\mathcal{L}f(X_t)]$$
&lt;p>展开期望积分形式：&lt;/p>
$$\frac{d}{dt}\int_{\mathbb{R}} f(x)p(x,t)dx = \int_{\mathbb{R}} \mathcal{L}f(x) \cdot p(x,t)dx$$
$$\int_{\mathbb{R}} f(x)\partial_t p(x,t)dx = \int_{\mathbb{R}} \mathcal{L}f(x) \cdot p(x,t)dx$$
&lt;hr>
&lt;p>对于 $\mathcal{L}f = af' + \frac{1}{2}b^2f''$：&lt;/p>
$$\int_{\mathbb{R}} \mathcal{L}f(x) \cdot p(x,t)dx = \int_{\mathbb{R}} \left[af' + \frac{1}{2}b^2f''\right]p dx$$
&lt;/br>
&lt;p>第一项分部积分（假设边界项消失）：&lt;/p>
$$\int_{\mathbb{R}} af' \cdot p \, dx = -\int_{\mathbb{R}} f \cdot \partial_x(ap) dx$$
&lt;/br>
&lt;p>类似地，第二项分部积分两次后：&lt;/p>
$$\int_{\mathbb{R}} \frac{1}{2}b^2f'' \cdot p \, dx = \int_{\mathbb{R}} f \cdot \frac{1}{2}\partial_{xx}(b^2p) dx$$
$$\begin{aligned}
\int_{\mathbb{R}} f(x)\partial_t p(x,t)dx &amp;= -\int_{\mathbb{R}} f \cdot \partial_x(ap) dx + \int_{\mathbb{R}} f \cdot \frac{1}{2}\partial_{xx}(b^2p) dx\\
&amp;= \int_{\mathbb{R}} f \left[-\partial_x(ap) + \frac{1}{2}\partial_{xx}(b^2p)\right] dx
\end{aligned}$$
&lt;/br>
&lt;p>由于这对所有 $f$ 成立，必有：&lt;/p>
$$\partial_t p = -\partial_x(ap) + \frac{1}{2}\partial_{xx}(b^2p)$$
&lt;/br>
&lt;p>这就是著名的 &lt;strong>Fokker-Planck 方程&lt;/strong>（也称为 &lt;strong>Kolmogorov 前向方程&lt;/strong>），其描述了概率密度如何随时间演化。&lt;/p>
&lt;h3 id="对偶性的意义">对偶性的意义&lt;/h3>
&lt;p>注意到：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Backward operator&lt;/strong>（向后方程的 generator）：$\mathcal{L}f = af' + \frac{1}{2}b^2f''$&lt;/li>
&lt;li>&lt;strong>Forward operator&lt;/strong>（向前方程的 generator）：$\mathcal{L}^* p = -\partial_x(ap) + \frac{1}{2}\partial_{xx}(b^2p)$&lt;/li>
&lt;/ul>
&lt;p>满足如下对偶性：&lt;/p>
$$\int_{\mathbb{R}} \mathcal{L}f \cdot p \, dx = \int_{\mathbb{R}} f \cdot \mathcal{L}^* p \, dx$$
&lt;p>虽然 backward 和 forward 方程表面上不同，但它们是对偶的，描述的是同一个过程的两个互补视角。&lt;/p>
&lt;h2 id="三个视角一个动力学">三个视角，一个动力学&lt;/h2>
&lt;p>现在我们可以总结一下：一个随机系统本质上可以从三个不同的角度观察：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>路径视角（SDE）&lt;/strong>：单条轨迹怎样演化，$dX_t = a(X_t)dt + b(X_t)dW_t$&lt;/li>
&lt;li>&lt;strong>函数视角（Generator）&lt;/strong>：可测函数的期望怎样变化，$d E[f(X_t)] = E[\mathcal{L}f(X_t)]dt$&lt;/li>
&lt;li>&lt;strong>密度视角（Fokker-Planck）&lt;/strong>：概率密度函数怎样演化，$\partial_t p = -\partial_x(ap) + \frac{1}{2}\partial_{xx}(b^2 p)$&lt;/li>
&lt;/ul>
&lt;p>这三个方程在本质上描述的是同一个动力学系统。&lt;/p>
&lt;hr>
&lt;p>简而言之概括性来看，更清晰的分层为：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Markov 过程&lt;/strong> → 最一般的随机过程，满足 Markov 性质&lt;/li>
&lt;li>&lt;strong>Semigroup&lt;/strong> → 由 Markov 过程导出的算子家族 $P_t$&lt;/li>
&lt;li>&lt;strong>Generator&lt;/strong> → Semigroup 的微分，是最本质的对象&lt;/li>
&lt;li>&lt;strong>Backward 方程&lt;/strong> → Generator 诱导的 PDE&lt;/li>
&lt;li>&lt;strong>Forward 方程&lt;/strong> → 密度演化的 PDE&lt;/li>
&lt;/ol>
&lt;p>只有当 generator 具有特殊的二阶微分形式时，我们才能构造对应的 SDE。很多重要的随机过程（如跳跃过程、稳定过程等）根本没有 SDE 表示，但它们有明确定义的 generator。&lt;/p></description></item><item><title>the left of us</title><link>https://topology2333.github.io/blog/posts/reading/left-of-us/</link><pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/reading/left-of-us/</guid><description>&lt;h2 id="起因">起因&lt;/h2>
&lt;p>读到了朋友的&lt;a href="https://ambiguoustr.github.io/posts/omakase-and-whats-left-for-us/">文字&lt;/a>。&lt;/p>
&lt;h2 id="引文">引文&lt;/h2>
&lt;p>如文字作者之所言，他创建的 SKILL: what’s left for us 的职能不过是盘 TODO。&lt;br>
曰：放大一个尺度，问的是同一件事。&lt;/p>
&lt;h2 id="正文">正文&lt;/h2>
&lt;p>工具循环流内的 PASS: what’s left for us 剥除的是待办的剩余。&lt;br>
原尺度下，剩余成了养料，我（它）&lt;strong>自食其果&lt;/strong>了。&lt;/p>
&lt;p>拉远一点。任务通过自动化流程一点点从【我】身上剥离后，【我】还有（剩）什么可做呢，【我】还是什么呢？&lt;br>
又或：我本剩余。&lt;/p>
&lt;p>叩问于剩余的剩余，以至于剩余之外。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-txt" data-lang="txt">&lt;span class="line">&lt;span class="cl">what&amp;#39;s left of
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> what&amp;#39;s left of
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> what&amp;#39;s left...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, there be &lt;strong>something&lt;/strong> for &lt;strong>nothing&lt;/strong>.&lt;br>
And the left &lt;strong>for&lt;/strong> us can be the left &lt;strong>of&lt;/strong> us.&lt;/p></description></item><item><title>范畴论小记</title><link>https://topology2333.github.io/blog/posts/math/cat/</link><pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/cat/</guid><description>&lt;h2 id="起因">起因&lt;/h2>
&lt;p>答辩完了，好舒服，让 LLM（流浪猫）带我学习。&lt;/p>
&lt;blockquote>
&lt;p>等等！&lt;br>
友人云：&lt;strong>单子不过是自函子范畴上的一个幺半群而&lt;/strong>已。何意味？&lt;/p>
&lt;/blockquote>
&lt;p>为了解答这个问题，我在流浪猫的带领下重构了段落。&lt;/p>
&lt;h2 id="基本概念">基本概念&lt;/h2>
&lt;p>所谓&lt;strong>幺半群&lt;/strong> (Monoid)，即一个集合上定义一个二元运算，存在单位元，并满足结合律。这个和范畴无关。&lt;/p>
&lt;p>（不过结合范畴定义可知，一个幺半群本身就可以看作是一个只有一个对象的范畴：态射就是幺半群的元素，态射的复合就是二元运算）&lt;/p>
&lt;hr>
&lt;p>下面解释&lt;strong>范畴&lt;/strong>的概念&lt;/p>
&lt;p>一个范畴 (Category) $\mathcal{C}$ 由以下三个要素组成：&lt;/p>
&lt;ul>
&lt;li>对象 (Objects)：$A, B, C$&lt;/li>
&lt;li>态射 (Morphisms)：$f: A \to B$&lt;/li>
&lt;li>组合律 (Composition)：考虑态射 $f: A \to B,\ g: B \to C$，必存在一个合态射 $g \circ f: A \to C$&lt;/li>
&lt;/ul>
&lt;p>另需满足：&lt;/p>
&lt;ul>
&lt;li>结合律：对于 $h \circ (g \circ f) = (h \circ g) \circ f$&lt;/li>
&lt;li>单位律：每个对象 $A$ 都存在一个态射 $id_A: A\to A$，使得 $\forall f, f\circ id_A = f = id_A \circ f$&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;strong>题外话&lt;/strong>：同构是特殊的态射。&lt;/p>
&lt;p>若对态射 $f: A \to B$ &lt;strong>存在&lt;/strong>态射 $g: B \to A$，s.t. $g \circ f = id_A$ 且 $f \circ g = id_B$&lt;br>
那么称：通过态射 $f$，$A$ 与 $B$ 是同构的。&lt;/p>
&lt;p>一点例子：&lt;/p>
&lt;ul>
&lt;li>集合范畴，集合为对象，集合间的映射为态射
&lt;ul>
&lt;li>双射且互为逆映射，那么两个集合在这个范畴意义下是同构的。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>群范畴，群为对象，群同态为态射
&lt;ul>
&lt;li>群同构对应的对象是同构的。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>偏序集范畴，自然数为对象，若 $n \le m$，构造 $n$ 到 $m$ 到一个态射。
&lt;ul>
&lt;li>同构对象只可能是同一个自然数。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>接下来在范畴之间定义 &lt;strong>函子&lt;/strong> (Functor)：$F: \mathcal{C} \to \mathcal{D}$&lt;/p>
&lt;ul>
&lt;li>把 $\mathcal{C}$ 里的每个对象 $A$ 变成 $\mathcal{D}$ 里的对象 $F(A)$。&lt;/li>
&lt;li>把 $\mathcal{C}$ 里的每个态射 $f: A \to B$ 变成 $\mathcal{D}$ 里的态射 $F(f): F(A) \to F(B)$。&lt;/li>
&lt;/ul>
&lt;p>注：函子本身也必须保持结合律和单位元性质。&lt;/p>
&lt;p>例如，考虑一个从 Grp (群范畴) 到 Set (集合范畴) 的函子 $U$（遗忘函子 Forgetful Functor）：对给定群，得到群所在的集合。&lt;/p>
&lt;hr>
&lt;p>自函子为映射到一个范畴自身的函子。自函子范畴 $[\mathcal{C}, \mathcal{C}]$ 的对象是所有的自函子，态射是这些函子之间的&lt;strong>自然变换&lt;/strong> (Natural Transformation)。&lt;/p>
&lt;p>对于 $\mathcal{C}$ 中的每一个对象 $X$，在 $\mathcal{D}$ 中都对应一个态射（箭头）：&lt;/p>
$$\alpha_X: F(X) \to G(X)$$
&lt;p>这个箭头被称为&lt;strong>自然变换&lt;/strong> $\alpha$ 在 $X$ 处的分量。&lt;/p>
&lt;p>需满足自然性条件 (Naturality Condition)：如果在 $\mathcal{C}$ 中有一个态射 $f: X \to Y$，那么在 $\mathcal{D}$ 中，以下两条路径的结果必须完全相同：&lt;br>
A：先通过 $\alpha_X$ 从 $F(X)$ 变到 $G(X)$，再通过 $G(f)$ 变到 $G(Y)$&lt;br>
B：先通过 $F(f)$ 从 $F(X)$ 变到 $F(Y)$，再通过 $\alpha_Y$ 变到 $G(Y)$。&lt;/p>
&lt;p>即：&lt;/p>
$$\begin{CD}
F(X) @>\alpha_X>> G(X) \\
@VF(f)VV @VVG(f)V \\
F(Y) @>>\alpha_Y> G(Y)
\end{CD}$$
&lt;p>即：$G(f) \circ \alpha_X = \alpha_Y \circ F(f)$。&lt;/p>
&lt;hr>
&lt;p>回到开篇那句话，函子复合就是这个范畴里的乘法运算。自函子范畴 $[\mathcal{C}, \mathcal{C}]$ 上的对象是自函子，&amp;ldquo;乘法&amp;quot;就是函子的复合 $\circ$：于是单子就是这个范畴上的幺半群。&lt;/p>
&lt;p>&lt;strong>单子&lt;/strong> (Monad) 的组成：&lt;/p>
&lt;ul>
&lt;li>一个自函子 $T: \mathcal{C} \to \mathcal{C}$&lt;/li>
&lt;li>单位自然变换 $\eta: Id \to T$（对应幺半群的单位元）&lt;/li>
&lt;li>乘法自然变换 $\mu: T^2 \to T$（对应幺半群的二元运算，这里 $T^2 = T \circ T$）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>由此&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>约定左复合 $T\mu$。其分量为：$(T\mu)_X = T(\mu_X)$。&lt;/li>
&lt;li>约定右复合 $\mu T$。其分量为：$(\mu T)_X = \mu_{T(X)}$。&lt;/li>
&lt;li>对于 $\eta$，有类似的约定。&lt;/li>
&lt;/ul>
&lt;p>这里视角比较巧妙，需要进一步的解释。&lt;/p>
&lt;ul>
&lt;li>具体而言，自然变换 $\mu: T^2 \Rightarrow T$ 为底层范畴 $\mathcal{C}$ 中的对象 $X$ 指派了一个底层的态射 $\mu_X$。所以 $\mu_X$ 是底层范畴 $\mathcal{C}$ 里的一个普通态射。由于 $T$ 是一个函子，且 $\mu_X$ 是 $\mathcal{C}$ 中的一个态射，那么根据函子的性质，$T$ 能把这个态射映射成另一个态射，而这个新态射就是&lt;strong>左复合&lt;/strong>的 $X$ 分量。&lt;/li>
&lt;li>自然变换 $\mu: T^2 \Rightarrow T$ 为底层范畴 $\mathcal{C}$ 中的任何对象指派一个态射。考虑&lt;strong>右复合&lt;/strong> $\mu T$ 在对象 $X$ 处的分量时，我们首先应用自函子 $T$ 于对象 $X$，得到底层范畴 $\mathcal{C}$ 中的一个新对象 $T(X)$。由于 $\mu$ 是一个自然变换，它为这个新对象 $T(X)$ 指派一个对应的底层态射。根据 $\mu$ 的定义，这个指派给对象 $T(X)$ 的态射就是 $\mu_{T(X)}$。这个态射的类型是从 $T^2(T(X))$ 指向 $T(T(X))$，即从 $T^3(X)$ 指向 $T^2(X)$。这个由 $X$ 诱导出的新态射 $\mu_{T(X)}$，就是&lt;strong>右复合&lt;/strong>的 $X$ 分量。&lt;/li>
&lt;/ul>
&lt;p>在定义了左右复合后，给出单子需要满足的交换条件：&lt;/p>
&lt;p>&lt;strong>结合律&lt;/strong>：
&lt;/p>
$$\begin{CD}
T^3 @>T\mu>> T^2 \\
@V\mu TVV @VV\mu V \\
T^2 @>>\mu> T
\end{CD}$$
&lt;p>即 $\mu \circ T\mu = \mu \circ \mu T$。&lt;/p>
&lt;p>&lt;strong>单位律&lt;/strong>（左箭头渲染有问题故如此别扭表示）：
&lt;/p>
$$\begin{CD}
T @>\eta T>> T^2 \\
@| @VV\mu V \\
T @= T
\end{CD}
\qquad
\begin{CD}
T @>T\eta>> T^2 \\
@| @VV\mu V \\
T @= T
\end{CD}$$
&lt;p>即 $\mu \circ \eta T = id_T = \mu \circ T\eta$。&lt;/p>
&lt;hr>
&lt;h2 id="代码">代码&lt;/h2>
&lt;p>以 Haskell 为例。&lt;/p>
&lt;hr>
&lt;p>自函子 $T$ $\leftrightarrow$ &lt;code>Functor&lt;/code>&lt;br>
在 Haskell 里，对象是类型（&lt;code>Int&lt;/code>, &lt;code>String&lt;/code>），态射是函数（&lt;code>a -&amp;gt; b&lt;/code>）。
自函子对应 &lt;code>Functor&lt;/code> 类型类，它提供了一个上下文（比如 &lt;code>Maybe&lt;/code> 或 &lt;code>[]&lt;/code>），并用 &lt;code>fmap&lt;/code> 映射态射：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">class&lt;/span> &lt;span class="kt">Functor&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="kr">where&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fmap&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>单位自然变换 $\eta$ (eta) $\leftrightarrow$ &lt;code>return&lt;/code> / &lt;code>pure&lt;/code>&lt;br>
$\eta$ 的作用是把底层范畴的一个普通对象 $X$，放入到自函子 $T$ 的上下文中，即 $Id \to T$。&lt;br>
在 Haskell 中对应 &lt;code>Monad&lt;/code> 的 &lt;code>return&lt;/code>：把一个普通值放入最小的默认上下文中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">return&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>乘法自然变换 $\mu$ $\leftrightarrow$ &lt;code>join&lt;/code>&lt;br>
$\mu$ 的作用是 $T^2 \to T$。
在 Haskell 里，$T^2$ 就是&lt;strong>嵌套了两次的上下文&lt;/strong>，如 &lt;code>Maybe (Maybe Int)&lt;/code> 或者 &lt;code>[[Int]]&lt;/code>。&lt;br>
$\mu$ 的作用是 Flatten，对应 &lt;code>Control.Monad&lt;/code> 里的 &lt;code>join&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">join&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Monad&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="ow">=&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>Haskell 里的单子标志性动作是 &lt;code>&amp;gt;&amp;gt;=&lt;/code> (bind)。&lt;strong>&lt;code>&amp;gt;&amp;gt;=&lt;/code> 可以看作是 &lt;code>fmap&lt;/code> 和 &lt;code>join&lt;/code> ($\mu$) 的组合&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;=&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Monad&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="ow">=&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">x&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">join&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fmap&lt;/span> &lt;span class="n">f&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>解释：&lt;/p>
&lt;ol>
&lt;li>考虑值 &lt;code>x :: m a&lt;/code> 和函数 &lt;code>f :: a -&amp;gt; m b&lt;/code>&lt;/li>
&lt;li>首先利用 &lt;code>fmap&lt;/code> 把函数 &lt;code>f&lt;/code> 应用到 &lt;code>x&lt;/code> 内部。因为 &lt;code>x&lt;/code> 本身有 &lt;code>m&lt;/code>，&lt;code>f&lt;/code> 又会产生一层 &lt;code>m&lt;/code>，所以 &lt;code>fmap f x&lt;/code> 的结果是嵌套的 &lt;code>m (m b)&lt;/code> （即 $T^2$）&lt;/li>
&lt;li>接着利用单子乘法 $\mu$（即 &lt;code>join&lt;/code>）把 &lt;code>m (m b)&lt;/code> 转换为 &lt;code>m b&lt;/code>（即 $T^2 \to T$）。&lt;/li>
&lt;/ol>
&lt;p>简而言之，&lt;code>bind&lt;/code> 操作可以理解为先做了一次自函子映射，然后执行了一次乘法运算。也就是嵌套成两层后拍平。&lt;/p>
&lt;hr>
&lt;p>意义：&lt;/p>
&lt;p>如果直接定义 &lt;code>m a -&amp;gt; m b&lt;/code> 会失去通用性。如果是 &lt;code>a -&amp;gt; b&lt;/code>，函数只关心业务逻辑（如加减法）；如果是 &lt;code>a -&amp;gt; m b&lt;/code>，函数只关心逻辑结果是否合法（如除法是否除以0）;如果是 &lt;code>m a -&amp;gt; m b&lt;/code>，函数还需要处理“输入 &lt;code>m a&lt;/code> 本身是否为空这种复杂任务。&lt;/p></description></item><item><title>De Bruijn index</title><link>https://topology2333.github.io/blog/posts/pl/de-bruijn-index/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/pl/de-bruijn-index/</guid><description>&lt;h2 id="目的">目的&lt;/h2>
&lt;p>考虑到 $\alpha$-等价性，$\lambda x. x$ 和 $\lambda y. y$ 在逻辑上是完全一样的，但写法不同，通过一套改写规则达到统一。&lt;/p>
&lt;hr>
&lt;h2 id="规则">规则&lt;/h2>
&lt;h3 id="定义">定义&lt;/h3>
&lt;p>改写后的符号用自然数标识，标识为从当前位置往外数，第几个 $\lambda$ 符号是我的绑定者。一些例子：&lt;/p>
&lt;ul>
&lt;li>$\lambda x. x$ 被改写成 $\lambda 1$&lt;/li>
&lt;li>$\lambda x. \lambda y. x$ 改写成 $\lambda \lambda 2$&lt;/li>
&lt;li>$\lambda x. \lambda y. \lambda z. x z (y z)$ 改写成 $\lambda \lambda \lambda 3 1 (2 1)$&lt;/li>
&lt;/ul>
&lt;p>形式化地，用 De Bruijn index 表示的 $\lambda$ 项，其语法为：&lt;/p>
$$M, N ::= n | M N | \lambda M$$
&lt;p>其中 $n$ 是大于 0 的自然数，表示变量；$M N$ 表示应用；$λ M$ 表示抽象。&lt;/p>
&lt;p>若变量 $n$ 处在至少 $n$ 个 $\lambda$ 的作用域内，则它是绑定变量；否则是自由变量。变量 $n$ 的绑定位置，是它所处作用域中从内向外数的第 $n$ 个 $\lambda$。&lt;/p>
&lt;hr>
&lt;h3 id="beta-规约">$\beta$ 规约&lt;/h3>
&lt;p>$(\lambda M) N$ 的 $\beta$-归约中，需要做三件事：&lt;/p>
&lt;ol>
&lt;li>找出 $M$ 中那些由最外层这个 $\lambda$ 绑定的变量；&lt;/li>
&lt;li>因为外层 $\lambda$ 被消去了，所以把 $M$ 中自由变量的编号整体减一；&lt;/li>
&lt;li>用参数 $N$ 替换对应位置，同时根据替换发生时所在的 $\lambda$ 层深，适当提升 $N$ 中自由变量的编号。&lt;/li>
&lt;/ol>
&lt;p>例子：$(\lambda\ \lambda\ 4\ 2\ (\lambda\ 1\ 3))\ (\lambda\ 5\ 1)$&lt;/p>
&lt;p>对应普通记号：$(\lambda x.\ \lambda y.\ z\ x\ (\lambda u.\ u\ x))\ (\lambda x.\ w\ x)$&lt;/p>
&lt;p>替换过程：&lt;/p>
&lt;ul>
&lt;li>标出将被替换的位置：$\lambda\ 4\ \Box\ (\lambda\ 1\ \Box)$&lt;/li>
&lt;li>外层 $\lambda$ 消失，自由变量编号整体减一：$\lambda\ 3\ \Box\ (\lambda\ 1\ \Box)$&lt;/li>
&lt;li>把方框 $\Box$ 替换为 $\lambda\ 5\ 1$，并根据所在 $\lambda$ 层提升自由变量编号：
&lt;ul>
&lt;li>第一个方框在 1 层 $\lambda$ 之下，因此替换成 $\lambda\ 6\ 1$&lt;/li>
&lt;li>第二个方框在 2 层 $\lambda$ 之下，因此替换成 $\lambda\ 7\ 1$&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>得到 $\lambda\ 3\ (\lambda\ 6\ 1)\ (\lambda\ 1\ (\lambda\ 7\ 1))$&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>替换的形式化定义：一个替换可以写成无限序列 $M_1.M_2.M_3\ldots$，其中第 $i$ 项 $M_i$ 表示第 $i$ 个自由变量将被替换成什么。所有相关变量编号加上 $k$ (shift) 记作 $\uparrow^k$。$\uparrow^0$ 是恒等替换。一个有限替换 $M_1.M_2.\ldots.M_n$ 实际是 $M_1.M_2.\ldots.M_n.(n+1).(n+2)\ldots$，也就是只替换前 $n$ 个变量，其余保持不变。&lt;/p>
&lt;p>替换作用记作 $M[s]$，替换的组合满足 $M[s_1\,s_2] = (M[s_1])[s_2]$。&lt;/p>
&lt;ul>
&lt;li>变量：第 $n$ 个变量在替换后变成第 $n$ 个替换项；&lt;/li>
&lt;li>应用：分别对左右两边替换；&lt;/li>
&lt;li>抽象：进入 $\lambda$ 以后，要把替换表整体向上调整一层
&lt;ul>
&lt;li>即 $(\lambda M)[s] = \lambda (M[1 . (s \uparrow^1)])$&lt;/li>
&lt;li>递归地，有 $(\lambda \lambda P)[s] = \lambda(\lambda P [1 . (s \uparrow^1)]) = \lambda (\lambda ( P [1 . 2 . (s \uparrow^2)] ))$&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>因此，$\beta$-归约可以简洁地写成 $(\lambda M)N \rightarrow M[N.1.2.3\ldots]$。例如，$(\lambda x. \lambda y. x)N$，也就是 $(\lambda \lambda 2)N$。$M = (\lambda 2)$，$s = N.1.2.3\dots$，要规约 $(\lambda 2)\ [N.1.2.3\dots]$。调整之后，$s' = 1 . (N[\uparrow^1]) . (1[\uparrow^1]) . (2[\uparrow^1]) \dots = 1 . (N\uparrow^1) . 2 . 3 . 4 \dots$。找到 $s'$ 的第二项，也就是 $N\uparrow^1$。所以，$(\lambda (\lambda 2))\ N$ 归约后的结果是 $\lambda (N\uparrow^1)$。&lt;/p>
&lt;p>对于 $\uparrow^1$ 的解释为：若 $(\lambda x. \lambda y. x)\ N$ 的 $N$ 中含有自由变量 $z$，那么它原来往上索引会超出最外层环境，而在 $\beta-$规约之后，最外层环境增加了一层，自由变量 shift 是非常合理的。&lt;/p>
&lt;hr>
&lt;h3 id="优缺点">优缺点&lt;/h3>
&lt;p>这样做的好处很明显：&lt;/p>
&lt;ul>
&lt;li>机器友好，判断两个表达式是否相等，只需要看数字序列是否一致，不需要进行复杂的变量更名（$\alpha$-conversion）。&lt;/li>
&lt;li>两个 $\alpha$-等价的项在内存中的表示是完全唯一的。可以直接通过哈希或简单的内存比较&lt;/li>
&lt;/ul>
&lt;p>但也有坏处：&lt;/p>
&lt;ul>
&lt;li>代换复杂：在进行 $\beta$-归约时，被代入项跨越的 $\lambda$ 层数发生变化，其内部所有指向外部的数字都必须进行加减校准。带来实现上的复杂度。&lt;/li>
&lt;li>人类难读。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="其他">其他&lt;/h2>
&lt;p>也有其他的策略，比如混合策略，对绑定变量使用 De Bruijn index 以方便计算，对自由变量使用名字以方便阅读。&lt;/p></description></item><item><title>扩散模型，蒸馏，以及量化</title><link>https://topology2333.github.io/blog/posts/reading/distilled-diffusion-quantization/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/reading/distilled-diffusion-quantization/</guid><description>&lt;h2 id="前因">前因&lt;/h2>
&lt;p>读到朋友写的一篇&lt;a href="https://aucannot.github.io/posts/distilled-diffusion-quantization/">博客&lt;/a>，又想起来一两年前我的随机过程数学老师建议我去学习一下 diffusion model。现在我打算写一篇博客。由于没有正经阅读 AI 文献，以下部分有一些是 Gemini-generated，注意甄别。&lt;/p>
&lt;h2 id="几个概念">几个概念&lt;/h2>
&lt;h3 id="文生图模型">文生图模型&lt;/h3>
&lt;p>以扩散模型（Diffusion Models） 为核心，并结合 Transformer 增强特征提取能力的复合架构。可以拆解为三个核心组件：Autoencoder（空间压缩）、Text Encoder（语义理解）和 Denoising Backbone（去噪骨架）。&lt;/p>
&lt;hr>
&lt;p>Autoencoder (VAE) 空间压缩的目标是学习两个映射函数：编码器 (Encoder) $\mathcal{E}$ 和 解码器 (Decoder) $\mathcal{D}$。&lt;/p>
&lt;p>具体来说，给定一张图像 $x \in \mathbb{R}^{H \times W \times 3}$，通过编码器 $\mathcal{E}$ 可以将其转换为一个低维的潜在表示 $z = \mathcal{E}(x)$，其中 $z \in \mathbb{R}^{h \times w \times c}$，且通常 $h \ll H, w \ll W$。随后，解码器 $\mathcal{D}$ 可以用来将 $z$ 恢复回原始图像空间，即 $\mathcal{D}(z) \approx x$。&lt;/p>
&lt;p>假设像素空间 $x$ 服从一个复杂的分布 $p(x)$，我们想要求潜变量的后验分布 $p(z|x)$；引入一个可计算的分布 $q_\phi(z|x)$（即编码器 $\mathcal{E}$）来逼近真实的后验分布。构造的数学目标是最小化 $q$ 与 $p$ 之间的 KL 散度。我们注意到对于一个给定的图像 $x$ 来说，$\log p(x)$ 是一个常数，满足：&lt;/p>
$$
\begin{aligned}
\log p(x) &amp;= \mathbb{E}_q \left[ \log \frac{p(x, z)}{p(z|x)} \right]\\
&amp;= \mathbb{E}_q \left[ \log \left( \frac{p(x, z)}{q_\phi(z|x)} \cdot \frac{q_\phi(z|x)}{p(z|x)} \right) \right]\\
&amp;= \underbrace{\mathbb{E}_q \left[ \log \frac{p(x, z)}{q_\phi(z|x)} \right]}_{\text{ELBO}} + \underbrace{\mathbb{E}_q \left[ \log \frac{q_\phi(z|x)}{p(z|x)} \right]}_{D_{KL}(q \| p)}
\end{aligned}
$$
&lt;p>$D_{KL} \geq 0$，所以第一项就是 $\log p(x)$ 的下界。我们最大化这个下界以最小化两个分布之间的 KL 散度：&lt;/p>
$$
\begin{aligned}
\text{ELBO} &amp;= \mathbb{E}_q [\log p(x|z) + \log p(z) - \log q_\phi(z|x)]\\
&amp;= {\mathbb{E}_q [\log p(x|z)]} - {\mathbb{E}_q \left[ \log \frac{q_\phi(z|x)}{p(z)} \right]}\\
&amp;= \underbrace{\mathbb{E}_{q_\phi(z|x)}[\log p_\theta(x|z)]}_{\text{重建项 (Reconstruction)}} - \underbrace{D_{KL}(q_\phi(z|x) \| p(z))}_{\text{正则项 (Regularization)}}
\end{aligned}
$$
&lt;/br>
&lt;p>反向传播要求损失函数对参数 $\phi$ 是可导的。编码器输出分布参数（比如均值 $\mu$ 和方差 $\sigma$）。$x \to \text{Encoder}(\phi) \to \text{Dist}(\mu, \sigma) \xrightarrow{\text{sampling}} z \to \text{Decoder} \to \text{Loss}$。&lt;/p>
&lt;p>然而在计算 $\partial \text{Loss}/\partial \phi$ 时，梯度必须流经 $z$ 到达 $\phi$。但 $z$ 是采样得到的，因而是不可导的。就像掷骰子的结果（$z$）和骰子（分布）的参数（$\mu, \sigma$）之间的数学关系是断裂的。无法写出一个确定性的函数 $f(\mu, \sigma)$ 来表示这个采样过程的导数。&lt;/p>
&lt;p>解决办法是从标准正态分布中采样一个噪声 $\epsilon \sim \mathcal{N}(0, 1)$。对于参数 $\phi$ 来说，$z$ 的表达式现在是完全可微的，而且还保持了分布&lt;/p>
$$z = \mu + \sigma \odot \epsilon\sim \mathcal{N(\mu,\epsilon)}, \quad \epsilon \sim \mathcal{N}(0, \mathbf{I})$$
&lt;/br>
&lt;p>其中 $\mu$ 和 $\sigma$ 是编码器 $\mathcal{E}$ 输出的两个向量。这样，随机性被转移到了 $\epsilon$ 上，而对 $\mu$ 和 $\sigma$ 的梯度可以顺畅地流回编码器。这个技巧叫做重参数化。&lt;/p>
&lt;p>这样一来，diffusion 就不再直接作用于像素空间 $x$，而是在潜在特征空间 $z$ 上进行，从而大幅减少了计算开销。&lt;/p>
&lt;hr>
&lt;p>Text Encoder 文本编码器是一个将离散文本转为连续向量空间的映射 $c = \tau_\theta(y)$。定义：给定提示词（Prompt）$y$，编码器 $\tau_\theta$（如 CLIP 或 T5）将其转换为条件向量 $c$。数学意义：$c$ 通常是一个序列向量 $\in \mathbb{R}^{L \times d}$，其中 $L$ 是 Token 长度，$d$ 是特征维度。为生图提供语义指导。&lt;/p>
&lt;hr>
&lt;p>去噪骨架是扩散模型的核心，数学上表现为一个噪声预测算子 $\epsilon_\theta$。在训练阶段，我们要最小化以下目标函数：&lt;/p>
$$\mathcal{L} = \mathbb{E}_{z, \epsilon \sim \mathcal{N}(0,1), t, c} \left[ \| \epsilon - \epsilon_\theta(z_t, t, c) \|^2_2 \right]$$
&lt;p>
&lt;/br>
变量含义：$z_t$：在 $t$ 时刻加噪后的潜变量。$c$：来自文本编码器的条件向量。$\epsilon_\theta$：神经网络（U-Net 或 Transformer），它学习预测注入到 $z$ 中的噪声 $\epsilon$。&lt;/p>
&lt;p>这里 Gemini 给出了 “$t$ 代表噪声强度”的论述。在我的追问下，它给出如下说明：通常定义一系列随时间 $t$ 变化的参数，通常记作 $\alpha_t$。$\alpha_0\sim 1,\alpha_T\sim 0$。参数化：&lt;br>
&lt;/p>
$$z_t = \sqrt{\bar{\alpha}_t} z_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon$$
&lt;p>
其中 $\epsilon \sim \mathcal{N}(0, \mathbf{I})$，开根号是为了保持新分布的方差不变。&lt;/p>
&lt;p>我的理解方式是，噪声是随机的，但去噪声的过程和 $c$ 有关，因而不是纯随机的，相当于带有一个 Guidance 的随机演化。&lt;/p>
&lt;hr>
&lt;h3 id="模型蒸馏">模型蒸馏&lt;/h3></description></item><item><title>Practical Foundations For Programming Languages</title><link>https://topology2333.github.io/blog/posts/pl/pfpl/</link><pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/pl/pfpl/</guid><description>&lt;h2 id="简介">简介&lt;/h2>
&lt;p>编程语言是由 Judgments 和 Inference Rules 定义的。Judgments 例如 “$e$ 是一个表达式（$e \text{ exp}$）” 或者 “$\tau$ 是一个类型（$\tau \text{ type}$）”。Inference Rules 的标记方法是：横线上方写前提（Premises）横线下方写结论（Conclusion）：&lt;/p>
$$\frac{\Gamma \vdash e_1 : \text{nat} \quad \Gamma \vdash e_2 : \text{nat}}{\Gamma \vdash e_1 + e_2 : \text{nat}}$$
&lt;/br>
&lt;p>这种嵌套结构最后会成为证明树（Proof Tree）。&lt;/p>
&lt;p>此外还有 Concrete Syntax 和 Abstract Syntax 的区别。前者是字符串，比如 1 + 1 或者 +(1, 1)。后者是抽象结构，可以使用抽象绑定树 (Abstract Binding Trees, ABTs) 来表示。比如表达式 1 + 2 在抽象语法中可能被表示为 plus(num[1]; num[2])。这种消歧表示直接体现了语言的逻辑结构。&lt;/p>
&lt;hr>
$$\frac{\Gamma \vdash e_1 : \text{bool} \quad \Gamma \vdash e_2 : \tau \quad \Gamma \vdash e_3 : \tau}{\Gamma \vdash \text{if } e_1 \text{ then } e_2 \text{ else } e_3 : \tau}$$
&lt;p>中间的 $e_1$ 必须是 bool 类型；两个分支 $e_2$ 和 $e_3$ 必须具有相同的类型 $\tau$。结果类型就是那个共同的类型 $\tau$。&lt;/p></description></item><item><title>矩阵微积分简明教程笔记</title><link>https://topology2333.github.io/blog/posts/math/gentle/</link><pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/gentle/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>朋友发来了一个名为 &lt;code>gentle.pdf&lt;/code> 的神秘文件。 中文翻译可以是《矩阵微积分简明教程》。遂写一份笔记。不同的是与原文章不同，这篇笔记会更偏张量语言。&lt;/p>
&lt;hr>
&lt;h2 id="笔记">笔记&lt;/h2>
&lt;p>视矩阵为二阶张量 $X^i_j$。&lt;/p>
&lt;p>那么 $\text {tr}(A) = A_i ^i$，于是迹的转置不变性显然。$\operatorname {tr}(AB) = \operatorname {tr}(A_i ^k B_k^j) = A_i^k B_k^i$。同理 $\operatorname {tr}(ABC) = \operatorname {tr}(A_i ^k B_k^j C_j^l) = A_i^k B_k^j C_j^i$。于是一列矩阵的积的迹在循环置换下的不变性显然。&lt;/p>
&lt;!-- 迹也可以看成 Frobenius Inner Product。$\operatorname{tr}(A) = \langle A, I \rangle = A_{ij} \delta_{ij} = A_{ii}$。 -->
&lt;p>线性型 $y^j = A_i^j x^i$。二次型 $f = A_{ij} x^i x^j$，注意到 $f = A'_{ji}x^jx^i = A'_{ij}x^ix^j$，因此 $f = (A_{ij}+A'_{ij}) x^ix^j/2$。也就是说，实际的有效输入只有 $(A+A')/2$。&lt;/p>
&lt;p>Kronecker 积 $C^{ik}_{jl} = (A \otimes B)^{ik}_{jl} = A^i _j B^k_l$，得到的应该是一个 $4-$dim 的张量。只不过原文按照矩阵的表示方法。自然地，$(A \otimes B)^{ik}_{jl} (C \otimes D)^{jl}_{mn} = (A^i_j B^k_l) (C^j_m D^l_n) = (A^i_j C^j_m) (B^k_l D^l_n) = (AC)^i_{\phantom{i}m} (BD)^k_{\phantom{k}n}$，即 $(A\otimes B)(C\otimes D) = (AC)\otimes (BD)$。&lt;/p>
&lt;p>vec 把一个指标对映射成一个单指标，根据原文的约定，对于 $\phi(i,j)$ 而言，前面的因子是慢指标，后面的因子是快指标（因为按列堆叠）。因此实际上对于矩阵 $A^i_j$ 而言，$\operatorname{vec} A$ 的行指标 $i$ 是快指标而列指标 $j$ 是慢指标，于是 $(\operatorname{vec} A)^{\phi (j,i)} = A^i_j$。而对于张量积的约定，很自然地有前面的指标是慢指标而后面的指标是快指标，于是很自然地有 $A^i_j\otimes B^k_l = C^{ik}_{jl} :\sim C^{\phi(i,k)}_{\phi(j,l)}$&lt;/p>
&lt;p>我们定义 $Y_l^i = A_j^iB_k^jC_l^k$，则 $(\text{vec}Y)^{\phi(l,i)} = Y_l^i = C_l^k A_j^i (\text{vec} B)^{\phi(k,j)} = (C'\otimes A)_{\phi(k,j)}^{\phi(l,i)} (\text{vec} B)^{\phi(k,j)}$。即：在实际矩阵运算中 $C'\otimes A$ 这个 $(2,2)$ 型张量的上下指标分别被线性化展平，形成一个矩阵；而 $B$ 和 $Y$ 也分别被展平成向量。因此 $\operatorname{vec}(ABC) = (C' \otimes A) \operatorname{vec}(B)$。&lt;/p>
&lt;p>对于原文中的 $K$，原定义为 $K \text{vec} A = \text{vec} A'$。这里采用张量的形式。按照上文约定，设 $B = A', x = \text{vec} A, y = \text{vec} B$，则 $y^{\phi(k,l)} = K^{\phi(k,l)}_{\phi(i,j)}x^{\phi(i,j)}$，且 $B^l_k = \delta_{jk}\delta^{il}A_i^j$。又 $B_k^l = K_{jk}^{il}A_i^j$，所以 $K_{jk}^{il} = \delta_{jk}\delta^{il}$。可以注意到这里的指标展平方式和前文两个矩阵的张量积的展平方式不同，一个简单的理解方式是，置换张量改变了指标的快慢次序。&lt;/p>
&lt;p>于是很自然地，当 $m = n$，对于输入坐标 $\phi(i,j)$ 和输出坐标 $\phi(k,l)$，迹要求二者相等，求解得 $(i,j)=(k,l)$，于是 $\text{tr}(K) = \delta_{ji}\delta^{ij} = \delta_i^i = n$。&lt;/p>
&lt;p>$K_{\phi(i,j)}^{\phi(k,l)} :\sim \delta_{jk}\delta^{il}, {K'} _{\phi(i,j)}^{\phi(k,l)} :\sim K_{\phi(k,l)}^{\phi(i,j)} :\sim \delta_{li}\delta^{kj}$，即得 $K$ 的对称性。又有 $(K^2)_{\phi(u,v)}^{\phi(k,l)} = \delta_{jk} \delta^{il}\delta_{vi}\delta^{uj} = \delta_k^u\delta_v^l$，即单位阵。&lt;/p>
&lt;p>下面证明交换性定理 The Commuting Property，即 $K(A\otimes B) = (B\otimes A)K$。设等式左边为 $L$ 右边为 $R$。则 $L_{jl}^{uv} = K_{ik}^{uv}A^i_jB^k_l = \delta^v_i\delta ^u_kA^i_jB^k_l = A^v_j B_l^u$，$R_{st}^{ki} = A^i_jB^k_lK_{st}^{lj} = A^i_jB^k_l\delta^l_t\delta ^j_s = A^i_s B_t^k$，$(u,v)\mapsto(k,i),(j,l)\mapsto(s,t)$，因此 $L=R$，得证。&lt;/p>
&lt;p>对于 $N_n = \frac{1}{2}(I+K)$，有 $N_n^2 = \frac{1}{4} (I+2K+K^2) = \frac{1}{4} (I+2K+I) = \frac{1}{2}(I+K) = N_n$。显然 $N_n$ 也满足 $N_n(A\otimes B) = (B\otimes A)N_n$。&lt;/p>
&lt;p>定义一个还原矩阵 $D_n$，把一个 $n(n+1)/2$ 维的向量填充在矩阵的下三角位置还原并对称还原原矩阵对应的拉直的向量。即 $D_n \text{vech}(A) = \text{vec}(A)$，此处 $A = A'$。于是 $K_nD_n \text{vech} X= K_n \text{vec} X = \text{vec} X' = \text{vec} X = D_n \text{vech} X$，即 $K_nD_n = D_n$。&lt;/p></description></item><item><title>随机分析笔记</title><link>https://topology2333.github.io/blog/posts/math/zermelo-game/</link><pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/zermelo-game/</guid><description/></item><item><title>强化学习简记</title><link>https://topology2333.github.io/blog/posts/machine-learning/rl/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/machine-learning/rl/</guid><description>&lt;p>起因：在实验室的书架上捞到一本强化学习（&lt;em>Richard &amp;amp; Andrew&lt;/em>），简单记录一下笔记。享受线性阅读和手打笔记的乐趣。&lt;/p>
&lt;h2 id="导论">导论&lt;/h2>
&lt;p>强调这本书是研究交互学习中的计算性方法，而不是直接建立关于人或动物如何学习的理论。&lt;/p>
&lt;p>试错和延迟收益是强化学习的两个最重要最显著的特征。&lt;/p>
&lt;p>当我们在提到强化学习（或者机器学习）的时候，要尤其注意这既表示一个问题，也表示解决问题的方法（甚至还表示一个领域）。不能混淆。&lt;/p>
&lt;p>与监督学习不同，在一个未知领域中的交互问题场景下，若想要做到收益最大，智能体必须能够从自身的经验中学习。与无监督学习也不同，强化学习的目标在于最大化收益信号，而不是找出数据的隐藏结构。&lt;/p>
&lt;p>强化学习有其独特的挑战：“试探”与“开发”的折中权衡。这个困境至今仍未被解决。另外一个关键的特征是明确了目标导向的智能体与不确定的环境交互这&lt;strong>整个&lt;/strong>问题，通过感知环境的各个方面，选择动作来影响它们所处的环境。强化学习既可以涉及规划也可以涉及监督学习，因而如果想要有效地进行强化学习算法的研究，必须对自问题进行单独的考虑和研究。&lt;/p>
&lt;p>强化学习和其他工程与科学学科之间有良好的互动，比如强化学习利用参数近似法解决了运筹学和控制论的研究中经典的“维度灾难”的问题。它和神经科学和心理学之间也有很强的相互作用。&lt;/p>
&lt;p>曾经基于一般规则的方法，比如搜索或学习，被定性为“弱方法”，而基于知识的方法则被称为“强方法”。强化学习研究无疑在追求更简单的人工智能普适原则。&lt;/p>
&lt;hr>
&lt;p>强化学习有四个核心要素：策略，收益信号，价值函数，（以及对环境建立的模型）。&lt;/p>
&lt;ul>
&lt;li>策略是强化学习智能体的核心，是环境状态到动作的映射。它可能是简单的查找表，也可以是一个复杂的搜索的过程。一般来说，策略可能是环境状态和智能体所采取动作的随机函数。&lt;/li>
&lt;li>收益信号是强化学习问题中的目标。收益信号表明了在短时间内什么是好的。&lt;/li>
&lt;li>价值函数表示了从长远角度来看什么是好的。状态的价值是一个智能体从这个状态开始，对将来累积的总收益的期望。&lt;/li>
&lt;/ul>
&lt;p>从某种意义上来说，收益更加重要，而收益预测的价值次之。然而，在制定或者评估策略的时候，我们更关心的是价值。动作是基于对价值的判断做出的，但是确定价值的难度要比确定收益难得多。收益基本上是由环境直接给予的，但是价值必须综合评估，并根据智能体在整个观察过程中观察到的收益序列重新估计。事实上，价值评估方法才几乎是所有强化学习算法中最重要的组成部分。&lt;/p>
&lt;ul>
&lt;li>对环境建立的模型是预测外部环境的下一个状态和下一个收益。环境模型会被用于做规划。简单的无模型方法是直接地试错，现代强化学习已经学会使用模型来进行规划。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>强化学习非常依赖“状态”的概念。状态既作为策略和价值函数的输入，又作为模型的输入与输出。一般来说，可以非正式地思考状态的含义，并且把它理解为当前智能体可知的环境信息。这本书不处理构建状态信号的问题，并不是因为状态的表征不重要，而是希望专注于策略问题。&lt;/p>
&lt;p>这本书对价值函数的估计进行了大量的讨论，但是一些优化算法，比如遗传算法、遗传规划、模拟退火算法以及其他算法也可以用来解决强化学习问题，而不用显式地估计价值函数。这些进化方法采用了大量静态策略，每个策略在扩展过的较长时间内与环境的一个独立实例进行交互，然后产生下一代。这类算法在当个个体的生命周期中不学习。如果决策空间足够小，或者可以很好地结构化地找到好的策略，或者智能体不能精确感知环境状态，那么进化方法是有效的（或者有优势的）。&lt;/p>
&lt;p>然而，进化方法忽视了强化学习问题中的一些有用结构：忽略了索求策略是状态到动作的函数这一事实，也没有注意个体在生命周期内的状态和动作的迭代。这本书认为进化方法就其自身而言不适合强化学习问题，因此不介绍。&lt;/p>
&lt;hr>
&lt;p>例子：井字棋。我们会将在贪心动作之后得到的状态所对应的价值“回溯更新”到动作之前的状态上。更准确地说，是对早先的状态的价值进行调整，使其更接近于后面的状态对应的价值。设 $S_t$ 表示在贪心动作之前的状态，$S_{t+1}$ 为转移之后的状态。价值函数用 $V(S_t)$ 来表示。那么：&lt;/p>
$$
V(S_t) \gets V(S_{t+1}) + \alpha \Big [ V(S_{t+1}) -V(S_t) \Big]
$$
&lt;p>$\alpha$ 称为步长参数，会影响学习速率。更新规则是时序差分的一个特例。如果步长参数随着时间的推移逐渐减小，对于任意固定对手，方法会收敛于最优策略下每个状态下真正的获胜概率。&lt;/p>
&lt;p>此时再和评估策略的进化方法进行比较：进化方法会忽略博弈中间的过程，进而当玩家获胜时，误认为这次游戏中的&lt;strong>所有&lt;/strong>动作都有功劳。而学习价值函数的过程利用了博弈过程中的可用信息。即使不用对手的模型，也不用显示地搜索所有可能的未来状态与动作的序列，简单的强化学习玩家也能针对短视的对手设置多步陷阱。&lt;/p>
&lt;p>&lt;strong>甚至强化学习理论也适用于连续时间问题。&lt;/strong>&lt;/p>
&lt;p>在一些场景下，神经网络为程序提供了从其经验中进行归纳的能力，因此在新的状态中，它根据保存的过去遇到的相似状态的信息来选择动作，并由神经网络来做出最后决策。在大的状态集中强化学习系统能起到多达作用，与它从过去的经验中进行总结推广的能力密切相关。对于这些问题，神经网络和深度学习&lt;strong>并不是&lt;/strong>唯一的，也不是最好的方法。&lt;/p>
&lt;p>可以先学习无模型的方法，再学习如何将他们作为更复杂的有模型方法的组成部分。&lt;/p>
&lt;p>关于左右互搏、对称性、贪心策略的，还有试探性学习可以进行更深入的讨论。&lt;/p>
&lt;hr>
&lt;p>强化学习是第一个严格意义上的解决从环境互动中学习以达到长期目标这一计算问题的领域。&lt;/p>
&lt;blockquote>
&lt;p>DOTO: 阅读强化学习早期历史&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;p>[P1] 代表表格型求解方法&lt;/p>
&lt;h2 id="p1-多臂赌博机">[P1] 多臂赌博机&lt;/h2>
&lt;h3 id="一个-k-臂赌博机问题">一个 $k$ 臂赌博机问题&lt;/h3>
&lt;p>考虑如下问题：你要重复地在 $k$ 个选项或动作中进行选择。每次做出选择，你都会得到一定数值的收益，收益由选择的动作决定的平稳概率分布产生。目标是在某一段时间内最大化总收益期望。也可以称之为老虎机。&lt;/p>
&lt;p>$k$ 个动作中的每一个在被选择时都有一个期望或者平均收益，称之为动作的&lt;strong>价值&lt;/strong>。记动作 $A_t$ 和收益 $R_t$，对任一动作 $a$ 的价值记作 $q_*(a)$ 是给定动作 $a$ 时收益的期望：&lt;/p>
$$
q_*(a):=\mathbb{E}[R_t|A_t=a]
$$
&lt;p>我们将对动作 $a$ 在 $t$ 时刻的价值的估计记作 $Q_t(a)$，我们希望它接近 $q_*(a)$。&lt;/p>
&lt;hr>
&lt;p>在 $k$ 臂赌博机问题和相关问题中，有很多复杂方法可以用来平衡开发和试探，但是这些方法中的很多都对平稳情况和先验知识做出了很强的假设。但在实际问题中，这些假设要么难以被满足，要么无法被验证。在这本书中我们更关心的是要不要去平衡它们。&lt;/p>
&lt;h3 id="动作-价值方法">动作-价值方法&lt;/h3>
&lt;p>一种估算价值的方法是根据计算实际收益的平均值：&lt;/p>
$$
Q_t(a) :=\frac{\sum_{i=1}^{t-1}R_i\mathbb{I}_{A_i=a}}{\sum_{i=1}^{t-1}\mathbb{I}_{A_i=a}}
$$
&lt;p>称这种方法为&lt;strong>平均采样方法&lt;/strong>，这是最简单的一种估值方法。&lt;/p>
&lt;p>接下来我们采用贪心 $A_t:=\argmax _a Q_t(a)$ 来选择动作。如果偶尔采用 $\varepsilon$ 的概率来等概率随机选择，称为 $\varepsilon$-贪心。&lt;/p>
&lt;h3 id="10-臂测试平台">10 臂测试平台&lt;/h3>
&lt;p>对于 10 个选择，每个选择的均值 $q_*(a)$ 从一个 0-1 高斯分布中生成，每个实际收益从 $\mathcal{N}(q_*(a),1)$ 中生成（平稳的情形）。&lt;/p>
&lt;p>TODO: 写代码验证。取不同的 $\varepsilon$，甚至变化的 $\varepsilon$，比如可以考虑随着时间递减 $\varepsilon$。&lt;/p>
&lt;h3 id="增量式实现">增量式实现&lt;/h3>
&lt;p>对于上面提到的平均采样方法，为了减少占用的内存，推导递推公式：&lt;/p>
$$
\begin{aligned}
Q_{n+1} &amp;= \frac{1}{n}\sum_{i\in 1..=n}R_i \\
&amp;=\frac{1}{n}\Big(R_n + (n-1)Q_n \Big) \\
&amp;=Q_n+\frac{1}{n}[R_n-Q_n]
\end{aligned}
$$
&lt;p>注意到这个形式和我们第一章井字棋更新估值函数的形式是一致的，这里的步长为 $1/n$，更一般地，我们记步长为 $\alpha_t(a)$。&lt;/p>
&lt;h3 id="跟踪一个非平稳问题">跟踪一个非平稳问题&lt;/h3>
&lt;p>上面我们讨论的都是平稳的，即收益的概率分布不随着时间变化的赌博机问题。但当我们遇到非平稳的强化学习问题时，给近期的收益赋予比过去很久的收益更高的权值就是一种合理的处理方式。最简单的方法是使用固定步长（如上文所述），展开后是 $Q_{n+1}$ 是关于 $R_i$ 的加权平均和，准确来说是指数近因加权平均。&lt;/p>
&lt;p>另外，对于 $\alpha_n(a)=1/n$，大数定律保证它可以收敛到真值。随机逼近理论中的一个著名结果给出了保证收敛概率为 1 的所需条件（&lt;em>Robbins–Monro&lt;/em>）&lt;/p>
&lt;blockquote>
&lt;p>TODO: 补充新的一页来给出 &lt;em>Robbins–Monro&lt;/em> 条件的理论与证明。&lt;/p>
&lt;/blockquote>
$$
\sum _{n=1} ^{\infin} \alpha_n(a) = \infin \land \sum _{n=1}^{\infin} \alpha^2_n(a) \lt \infin
$$
&lt;p>第一个条件需要保证足够大的步长，克服任何初始条件或随机波动。第二个条件保证最终步长变小，以保证收敛。&lt;/p>
&lt;p>尽管在理论中常常用到，但是符合这个条件的步长参数序列往往收敛得很慢，而且或者需要大量调试才能得到一个满意的收敛率。&lt;/p>
&lt;blockquote>
&lt;p>TODO: 编程证实采用采样平均方法解决非平稳问题的困难。使用 10 臂测试，其中所有 $q_*(a)$ 初始值相等，然后进行随机游走。在每一步，所有的 $q_*(a)$ 加上一个 $\mathcal{N}(0,0.01)$ 生成的增量。对比：(1)采样平均-增量步长计算(2)常数步长($\alpha=0.1$)-动作-价值。$\epsilon=0.1$&lt;/p>
&lt;/blockquote>
&lt;h3 id="乐观初始值">乐观初始值&lt;/h3>
&lt;p>初始值可以设置预期的收益的先验知识。此外，设置一个乐观的初始值也容易让学习器感到“失望”，从而转向其他的动作，进而导致所有动作在估计值收敛之前都被尝试了好几次，系统会进行大量的试探。&lt;/p>
&lt;p>乐观初始值在开始可能会表现得很差，但是随着时间的推移，试探的次数逐渐减少，它也会表现得更好。这在平稳问题中非常有效。但它不太适合非平稳问题，因为它试探的驱动力是暂时的。采样平均也是把开始时间当作一个特殊的时间点，用相同权重去平均后续的收益。但是事实上，任何仅仅关注初始条件的方法都不太可能对一般的非平稳情况有帮助，因为开始时刻只出现一次。&lt;/p>
&lt;blockquote>
&lt;p>无偏恒定步长技巧&lt;/p>
&lt;/blockquote>
&lt;p>平均采样相较于恒定步长的好处在于，它不会像恒定步长那样产生偏差。然而平均采样在非平稳问题上表现得很差。一种可行的，利用了恒定步长在非平稳过程中的优势并且避免了它的偏差的方法是：针对某个特定动作的第 $n$ 个收益&lt;/p>
$$
\begin{aligned}
\beta _n &amp;:= \alpha / \bar{o} _n \\
\bar{o}_n &amp;:= \bar{o}_{n-1} +\alpha (1-\bar{o}_{n-1})
\end{aligned}
$$
&lt;h3 id="基于置信度上界的动作选择">基于置信度上界的动作选择&lt;/h3>
&lt;p>&lt;em>upper confidence bound&lt;/em> 置信上界&lt;/p>
$$
A_t:= \argmax _a \Big [ Q_t(a) + c\sqrt{\frac{\ln t}{N_t(a)}} \Big]
$$
&lt;p>其中 $N_t(a)$ 表示在时刻 $t$ 之前 $a$ 动作被选择的次数。&lt;/p>
&lt;blockquote>
&lt;p>TODO: 解释尖峰及其出现时刻&lt;/p>
&lt;/blockquote>
&lt;h3 id="梯度赌博机算法">梯度赌博机算法&lt;/h3>
&lt;p>考虑对每一个动作 $a$ 学习一个数值化的偏好函数 $H_t(a)$。利用这个 $H_t(a)$ 和玻尔兹曼分布确定动作概率：&lt;/p>
$$
\Pr\{A_t=a\} := \frac{e^H_t(a)}{\sum_{i=1}^ke^{H_t(i)}} := \pi_t(a)
$$
&lt;p>$\pi_t(a)$ 表示被选择的概率。进而提出一种自然学习算法：当选择动作 $A_t$ 并获得收益 $R_t$ 后，偏好函数的更新方式为&lt;/p>
$$
\begin{aligned}
H_{t+1}(A_t) &amp;:= H_t(A_t) + \alpha (R_t - \bar{R} _t)(1-\pi_t(A_t)), \\
H_{t+1}(a) &amp;:=H_t(a) -\alpha (R_t - \bar{R} _t)\pi_t(a), \quad a\neq A_t
\end{aligned}
$$
&lt;p>其中 $\alpha$ 是步长，$\bar{R}_t$ 表示在时刻 $t$ 内所有收益的平均值，作为比较收益的基准项。&lt;/p>
&lt;hr>
&lt;p>通过随机梯度上升实现梯度赌博机算法&lt;/p>
&lt;p>在精确的&lt;strong>梯度随机上升算法&lt;/strong>中，每一个动作的偏好函数 $H_t(a)$ 与增量对性能的影响成正比。&lt;/p>
$$
\begin{aligned}
H_{t+1}(a)-H_t(a) &amp;= \alpha \frac{\partial \mathbb{E}[R_t]}{\partial H_t(a)} \\
\mathbb{E}[R_t] :&amp;= \sum_x \pi_t(x)q_*(x)
\end{aligned}
$$
&lt;p>当然，因为 $q_*(x)$ 未知，因此不可能实现真正精确的随机梯度上升算法。注意到 $\sum_x\pi_t(x) = 1$，因而 $\sum _x \partial \pi_t(x)/\partial H_t(a) = 0$，所以：&lt;/p>
$$
\begin{aligned}
\frac{\partial \mathbb{E}[R_t]}{\partial H_t(a)} &amp;= \sum_x q_*(x) \frac{\partial\pi_t(x)}{\partial H_t(a)} \\
&amp;=\sum_x (q_*(x)-B_t) \frac{\partial\pi_t(x)}{\partial H_t(a)} \\
&amp;=\sum_x \pi_t(x) \frac{(q_*(x)-B_t) \frac{\partial\pi_t(x)}{\partial H_t(a)}}{\pi_t(x)}\\
&amp;=\mathbb{E}\Big [(q_*(A_t)-B_t)\frac{\partial\pi_t(A_t)}{\partial H_t(a)}/{\pi_t(A_t)} \Big]\\
&amp;=\mathbb{E}\Big [(R_t-\bar{R}_t)\frac{\partial\pi_t(A_t)}{\partial H_t(a)}/{\pi_t(A_t)} \Big]
\end{aligned}
$$
&lt;p>关于最后一步的解释：&lt;/p></description></item><item><title>质数筛</title><link>https://topology2333.github.io/blog/posts/math/eular-shieve/</link><pubDate>Sun, 04 Jan 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/eular-shieve/</guid><description>&lt;h2 id="两种筛的分析">两种筛的分析&lt;/h2>
&lt;blockquote>
&lt;p>感谢 @&lt;a href="https://github.com/LS-Hower">LS-Hower&lt;/a> 的勘误&lt;/p>
&lt;/blockquote>
&lt;h3 id="埃拉托斯特尼筛">埃拉托斯特尼筛&lt;/h3>
&lt;p>对于每一个质数 $p$，标记其倍数 $p^2,p(p+1),p(p+2)\cdots$，对于足够大的 $n$ 和固定的 $p$，标记次数约为
&lt;/p>
$$T(n)\sim \sum_{p\le n} \frac{n}{p} = n\sum _{p\le n}\frac{1}{p}$$
&lt;p>
利用质数调和级数的渐近式
&lt;/p>
$$\sum _{p\le n} \frac{1}{p} = \log\log n+B+o(1)$$
&lt;p>
从而 $T(n)=n\log\log(n)$。有一个 $\log\log n$ 的因子，说明重复访问。&lt;/p>
&lt;hr>
&lt;h3 id="欧拉筛">欧拉筛&lt;/h3>
&lt;p>记 $\text{lp}(n)$ 为 $n$ 的最小质因子（&lt;em>least prime factor&lt;/em>）。由最小质因数引入划分 $\mathcal{L}_p=\{n\ge 2;\text{lp}(n)=p\}$。&lt;/p>
&lt;p>由二元对 $(n,p)$ 双射地标记一个合数 $x=n\times p$，满足性质 $\text{lp}(x)=p$。&lt;/p>
&lt;p>为了给出遍历算法，我们让 $n$ 自然生长，而限制 $p\in \mathcal{P}$ 的范围 $p\le \text{lp}(n)$，以保持 $\text{lp}(x)=p$ 的性质。每个合数仅被其最小质因子筛去一次，因此算法时间复杂度为 $O(n)$。&lt;/p>
&lt;hr>
&lt;dl>
&lt;dt>$p\le \text{lp}(n)$ 的证明&lt;/dt>
&lt;dd>设 $p_n:=\text{lp}(n)$，那么 $\text{lp}(n\times p)=\min\{p,p_n\} = p$，说明当 $p \leq \text{lp}(n)$ 时，性质得到延续。&lt;/dd>
&lt;dd>若 $p\gt p_n$，$\text{lp}(n\times p)=\min\{p,p_n\} = p_n\neq p$，性质被破坏。此时得到的合数 $n\times p$ 应该隶属于 $\mathcal{L}_{p_n}$ 而非 $\mathcal{L}_p$，因此不应继续用更大的质数去枚举这个 $n$ 的后继标记。&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;h2 id="积性函数的自然分解">积性函数的自然分解&lt;/h2>
&lt;h3 id="基本概念">基本概念&lt;/h3>
&lt;p>$f:\mathbb{N}\to \mathbb{C}$ 称积性的，如果对于互质的 $m,n$，$f(mn) = f(m)f(n)$。&lt;br>
如果对所有 $m,n$ 都满足，则称为完全积性的。&lt;/p>
&lt;p>定义狄利克雷卷积&lt;/p>
$$ (f*g)(n):= \sum_{d \mid n}f(d)g \Big(\frac{n}{d} \Big ) $$
&lt;p>狄利克雷卷积可以保持积性。&lt;/p>
&lt;p>定义 $\varepsilon := \llbracket n=1\rrbracket, 1(n) = 1, \text{id}(n)=n$，那么 $1*\mu=\varepsilon, 1*1=\tau, \text{id}*1=\sigma, 1*\varphi=\text{id}$&lt;/p>
&lt;hr>
&lt;h2 id="一点代码">一点代码&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">fn&lt;/span> &lt;span class="nf">linear_sieve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>: &lt;span class="kt">usize&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>-&amp;gt; &lt;span class="p">(&lt;/span>&lt;span class="nb">Vec&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">usize&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Vec&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">usize&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="fm">vec!&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="k">usize&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// least prime factor
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">primes&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Vec&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">..=&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">primes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">primes&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lp&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">primes&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-haskell" data-lang="haskell">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Control.Monad&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Control.Monad.ST&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Array.ST&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nn">Data.Array.Unboxed&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="nf">linearSieve&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">UArray&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="kt">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="kt">Int&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">linearSieve&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="ow">=&lt;/span> &lt;span class="n">runST&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lp&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">newArray&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="ow">::&lt;/span> &lt;span class="kt">ST&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">STUArray&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="kt">Int&lt;/span> &lt;span class="kt">Int&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">primesRef&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">newSTRef&lt;/span> &lt;span class="kt">[]&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="n">forM_&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="nf">\&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">v&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">readArray&lt;/span> &lt;span class="n">lp&lt;/span> &lt;span class="n">i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">when&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">writeArray&lt;/span> &lt;span class="n">lp&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="n">i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">modifySTRef&lt;/span> &lt;span class="n">primesRef&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="kt">:&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="n">primes&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">readSTRef&lt;/span> &lt;span class="n">primesRef&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lpi&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">readArray&lt;/span> &lt;span class="n">lp&lt;/span> &lt;span class="n">i&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="n">forM_&lt;/span> &lt;span class="n">primes&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="nf">\&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="ow">-&amp;gt;&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">when&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">lpi&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">$&lt;/span> &lt;span class="kr">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">writeArray&lt;/span> &lt;span class="n">lp&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">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="n">lp&amp;#39;&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">freeze&lt;/span> &lt;span class="n">lp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">primes&lt;/span> &lt;span class="ow">&amp;lt;-&lt;/span> &lt;span class="n">fmap&lt;/span> &lt;span class="n">reverse&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">readSTRef&lt;/span> &lt;span class="n">primesRef&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lp&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>随机分析笔记</title><link>https://topology2333.github.io/blog/posts/math/random/</link><pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/math/random/</guid><description>&lt;h2 id="概率论基础">概率论基础&lt;/h2>
&lt;p>$\Omega$ 是非空集合，$\mathcal{F}$ 是 $\Omega$ 的子集族。&lt;/p>
&lt;hr>
&lt;dl>
&lt;dt>$\sigma$ 代数&lt;/dt>
&lt;dd>称 $\mathcal{F}$ 是一个 $\sigma$ 代数，如果：&lt;/dd>
&lt;dd>(1) $\varnothing\in\mathcal{F}$&lt;/dd>
&lt;dd>(2) $A \in \mathcal{F} \implies A^c \in \mathcal{F}$&lt;/dd>
&lt;dd>(3) $A_1, A_2 \cdots \in \mathcal{F} \implies \bigcup _{n=1}^{\infin} A_n\in \mathcal{F} $&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>概率&lt;/dt>
&lt;dd>概率测度 $\mathbb{P} :\mathcal F \to [0,1],A\mapsto \mathbb{P} (A)$。称 $\mathbb{P} (A)$ 为 $A$ 的概率，三元组 $(\Omega,\mathcal{F} ,\mathbb{P})$ 为一个概率空间。要求：&lt;/dd>
&lt;dd>(1) $\mathbb{P} (\Omega) = 1$&lt;/dd>
&lt;dd>(2) $A_i \in \mathcal{F}, A_i \cap A_j = \varnothing \implies \mathbb{P} \Big(\bigcup _{n=1}^{\infin} A_n\Big) = \sum _{n=1} ^{\infin} \mathbb{P} (A_n)$&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>均匀测度&lt;/dt>
&lt;dd>也称勒贝格测度 $\mathcal{L}$。&lt;/dd>
&lt;dd>由全体闭区间出发而生成的 $\sigma$ 代数称为 [0,1] 的子集的 Borel $\sigma$ 代数，记为 $\mathcal{B}([0,1])$。&lt;/dd>
&lt;dd>$\mathbb{P}([a,b]) = b-a, 0\le a\le b\le 1$&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>几乎必然&lt;/dt>
&lt;dd>设 $(\Omega,\mathcal{F} ,\mathbb{P})$ 是一个概率空间。如果 $A\in \mathcal{F} \land \mathbb{P}(A) = 1$，称事件 $A$ 几乎必然发生。&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>随机变量&lt;/dt>
&lt;dd>设 $(\Omega,\mathcal{F} ,\mathbb{P})$ 是一个概率空间。称 $X: \Omega\to \mathbb{R}$ 是一个随机变量，如果：&lt;/dd>
&lt;dd>(1) $B\in \mathbb{B}(\mathbb{R}) \implies \{X\in B\} := \{\omega \in \Omega; X(\omega)\in B \}\in \mathcal{F} $&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>分布测度&lt;/dt>
&lt;dd>设 $X$ 是概率空间 $(\Omega,\mathcal{F} ,\mathbb{P})$ 上的一个随机变量。$X$ 的分布测度是一个概率测度&lt;/dd>
&lt;dd>$\mu_X: \mathbb{B}(\mathbb{R}) \to [0,1], \mu_X(B) \mapsto \mathbb{P} \{X\in B\} $&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>指示函数&lt;/dt>
&lt;dd>当 $\omega\in A$ 时 $\mathbb{I}_A(\omega) = 1$ 否则 $\mathbb{I}_A(\omega)=0$。随机变量 $\mathbb{I}_A$ 称为集合 $A$ 的指示函数。对于 $A\sub \Omega$，定义
$$\int _A X(\omega) d \mathbb{P}(\omega) = \int _\Omega \mathbb{I}_A(\omega) X(\omega) d \mathbb{P}(\omega)$$&lt;/dd>
&lt;/dl>
&lt;hr>
&lt;dl>
&lt;dt>期望&lt;/dt>
&lt;dd>$X$ 是在 $(\Omega,\mathcal{F} ,\mathbb{P})$ 上的随机变量。$X$ 的期望定义为：&lt;/dd>
&lt;dd>$$\mathbb{E}(X) = \int _\Omega X(\omega)d\mathbb{P}(\omega)$$&lt;/dd>
&lt;dd>有詹森不等式：$\varphi$ 是 $\mathbb{R}$ 上的实值凸函数且 $\mathbb{E}(X)\le \infin$，则 $\varphi(\mathbb{E}X)\le \mathbb{E}\varphi(X)$&lt;/dd>
&lt;/dl>
&lt;h2 id="信息和条件期望">信息和条件期望&lt;/h2></description></item><item><title>L3 cache mapping on Sandy Bridge CPUs, Mark Seaborn</title><link>https://topology2333.github.io/blog/posts/l3-cache/</link><pubDate>Tue, 14 Jan 2025 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/l3-cache/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>出自这篇 &lt;a href="https://lackingrhoticity.blogspot.com/2015/04/l3-cache-mapping-on-sandy-bridge-cpus.html">博客&lt;/a>, by Mark Seaborn, on Monday, 27 April 2015&lt;/p>
&lt;h2 id="简介">简介&lt;/h2>
&lt;p>Sandy Bridge 处理器的 L3 缓存（三级缓存）是多个核心共享的，通常位于每个处理器模块内。每个模块包含两个核心，多个模块构成一个处理器。L3 缓存的大小通常为 3MB、6MB 或 8MB，根据处理器型号的不同而有所不同。&lt;br>
在一些测试中，Sandy Bridge 的 L3 缓存使用的是分布式环形结构（NUCA, Non-Uniform Cache Architecture），不同核心之间可以共享缓存。由于这种架构，缓存访问的延迟会根据物理地址的映射和访问的核心而有所不同。&lt;/p>
&lt;p>L3 缓存映射：Sandy Bridge 的 L3 缓存被划分为多个缓存切片，每个核心对应一个缓存切片，处理器通过物理地址哈希算法决定每个地址映射到哪个缓存切片。这一机制是对内存访问的优化，减少了不同核心之间访问共享缓存的延迟。&lt;br>
行锤攻击：Sandy Bridge 的缓存架构和内存控制器特性可能被用于行锤攻击（Row Hammering）。在这种攻击中，攻击者可以通过频繁访问内存中的某些行，诱使 DRAM 出现位翻转，这可能导致数据损坏或安全漏洞。&lt;/p>
&lt;blockquote>
&lt;p>然而我的老电脑 Intel Core i5-7200U 不属于这个结构，而是 &lt;a href="https://www.intel.com/content/www/us/en/products/sku/95443/intel-core-i57200u-processor-3m-cache-up-to-3-10-ghz/specifications.html">Kaby Lake&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="原文概述">原文概述&lt;/h2>
&lt;p>2013 年，一些研究人员逆向工程了 Intel Sandy Bridge CPU 如何将物理地址映射到 L3 缓存（最后一级缓存）中的缓存集合&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>。他们对缓存映射感兴趣，因为它可以用来绕过内核的 ASLR&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>。博客作者感兴趣的原因是，the cache mapping can be used to test whether cached memory accesses can do row hammering.&lt;/p>
&lt;h3 id="some-background">Some background&lt;/h3>
&lt;p>在 Sandy Bridge CPU 上，L3 缓存被划分为多个切片。物理地址通过哈希函数决定它们将存储在哪个 L3 缓存切片中。&lt;/p>
&lt;p>L3 缓存是分布式的，并且基于环形结构。每个核心有一个切片，但 CPU 中的所有核心都可以通过环形总线访问所有的缓存切片，环形总线将所有核心及其缓存连接在一起。&lt;/p>
&lt;p>当一个核心访问内存位置时，如果该位置映射到另一个核心的缓存切片上，访问速度会稍微变慢，因为需要绕过环形总线进行一到两次跳跃才能访问该位置。环形总线上使用的协议基于 QPI&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>&lt;/p>
&lt;p>每个缓存切片包含 2048 个缓存集合。在低端 CPU 上，缓存集合是 12 路关联的，因此一个缓存切片的大小为 1.5MB（2048 个集合，12 路 每个缓存行 64 字节 = 1.5MB）；在高端 CPU 上，缓存集合是 16 路关联的，因此一个缓存切片的大小为 2MB。&lt;/p>
&lt;h3 id="cache-mapping">Cache mapping&lt;/h3>
&lt;p>研究人员（Hund 等人）发现，L3 缓存使用物理地址的位如下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>位 0-5&lt;/strong>：这 6 位表示在 64 字节缓存行内的字节偏移。&lt;/li>
&lt;li>&lt;strong>位 6-16&lt;/strong>：这 11 位表示缓存切片内的缓存集编号。&lt;/li>
&lt;li>&lt;strong>位 17-31&lt;/strong>：这些位经过哈希运算，决定使用哪个缓存切片。&lt;/li>
&lt;li>&lt;strong>位 32 及以后&lt;/strong>：未使用。&lt;/li>
&lt;/ul>
&lt;p>选择缓存切片的哈希函数如下：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>在 4 核 CPU 上&lt;/strong>，有 4 个缓存切片，因此切片号是 2 位。切片号的两个位分别是 h1 和 h2，其中：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>h1&lt;/strong> 是物理地址位 18、19、21、23、25、27、29、30、31 的 XOR。&lt;/li>
&lt;li>&lt;strong>h2&lt;/strong> 是物理地址位 17、19、20、21、22、23、24、26、28、29、31 的 XOR。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>在 2 核 CPU 上&lt;/strong>，有 2 个缓存切片，因此切片号是 1 位。切片号是物理地址位 17、18、20、22、24、25、26、27、28、30 的 XOR。这等同于 &lt;strong>h1&lt;/strong> 和 &lt;strong>h2&lt;/strong> 的 XOR。（位 19、21、23、29 和 31 在 XOR 计算时会相互抵消，这部分是博客作者发现的内容）&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="verifying-the-cache-mapping">Verifying the cache mapping&lt;/h3>
&lt;p>步骤如下：&lt;/p>
&lt;ul>
&lt;li>选择 N 个物理内存地址，这些地址根据我们猜测的缓存映射应该映射到同一个缓存集。&lt;/li>
&lt;li>使用 Linux 的 /proc/PID/pagemap 接口来确定我们可以访问哪些物理地址。&lt;/li>
&lt;li>测量访问这些 N 个地址所需的时间。具体来说，程序首先访问前 N-1 个地址，然后测量访问第 N 个地址的时间。&lt;/li>
&lt;li>程序针对多个 N 值进行测试。&lt;/li>
&lt;/ul>
&lt;p>如果正确猜测了缓存映射，那么，在具有 12 路缓存的 CPU 上，我们应该会看到在 N=13 时，内存访问时间大幅上升。这是因为，在 N=13 时，我们访问的内存位置已经不再适合 12 路缓存集，导致 L3 cache miss。内存访问时间将从 L3 缓存的延迟增加到 DRAM 的延迟。&lt;/p>
&lt;blockquote>
&lt;p>注意： 这也假设缓存使用 LRU or Pseudo-LRU eviction policy（Sandy Bridge 使用的策略）。然而，Ivy Bridge 的 cache eviction policy 发生了变化。&lt;/p>
&lt;/blockquote>
&lt;p>如果我们猜错了缓存映射，内存访问时间将以 N 的较高值逐渐上升。在一个 2 缓存片 CPU 上，如果我们得到的地址到片散列函数错误，我们将看到访问时间达到 DRAM 延迟 N = 13 * 2，平均，因为 N 个物理地址将分布在 2 个片上，所以在片上的 2 个缓存集溢出并产生缓存丢失之前，平均需要 13 * 2 个地址。&lt;/p>
&lt;h3 id="ivy-bridge">Ivy Bridge&lt;/h3>
&lt;p>这种 L3 缓存映射似乎同样适用于 Ivy Bridge 系列的 CPU。作者在配有 Ivy Bridge CPU 的机器上运行了相同的测试（2-core, 4-hyperthread），最初得到了相同的图形结果。然而，这些结果在该机器上并没有稳定复现。后续的测试显示，在 N&amp;lt;=12 时，内存访问时间更高。&lt;/p>
&lt;p>这与报告一致，说明 Ivy Bridge 的 L3 缓存使用了 DIP (Dynamic Insertion Policy)&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> 作为其 cache eviction policy，以避免 cache thrashing。DIP 会在 LRU 和 BIP 之间动态切换：LRU 更适用于较小的工作集（可以完全装入缓存），而 BIP 更适用于较大的工作集（无法完全装入缓存）。对于 N&amp;gt;12，作者的测试可能会产生足够的缓存未命中，从而导致缓存切换到 BIP 模式。这意味着测试 N 值的顺序可能会影响最终结果。&lt;/p>
&lt;h3 id="thanks">Thanks&lt;/h3>
&lt;p>Thanks to Yossef Oren for pointing me to the paper by Hund et al, which is referenced by the paper he coauthored, &amp;ldquo;The Spy in the Sandbox &amp;ndash; Practical Cache Attacks in Javascript&amp;rdquo; (Yossef Oren, Vasileios P. Kemerlis, Simha Sethumadhavan, Angelos D. Keromytis).&lt;/p>
&lt;blockquote>
&lt;p>附原作者致谢&lt;/p>
&lt;/blockquote>
&lt;h2 id="源码httpsgithubcomgooglerowhammer-testblob9a426c30ac2cc1bad0a6714e3e75e763bfdee4eacache_analysiscache_test_physaddrcc阅读">&lt;a href="https://github.com/google/rowhammer-test/blob/9a426c30ac2cc1bad0a6714e3e75e763bfdee4ea/cache_analysis/cache_test_physaddr.cc">源码&lt;/a>阅读&lt;/h2>
&lt;h3 id="frame_number_from_pagemap-init_pagemap-get_physical_addr">frame_number_from_pagemap, init_pagemap, get_physical_addr&lt;/h3>
&lt;div class="highlight">&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">// Extract the physical page number from a Linux /proc/PID/pagemap entry.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">frame_number_from_pagemap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">value&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">return&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="mi">1ULL&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">54&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 class="c1">// 保留低 54 位
&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="kt">void&lt;/span> &lt;span class="nf">init_pagemap&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">g_pagemap_fd&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/proc/self/pagemap&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">O_RDONLY&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g_pagemap_fd&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">get_physical_addr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uintptr_t&lt;/span> &lt;span class="n">virtual_addr&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="kt">uint64_t&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">off_t&lt;/span> &lt;span class="n">offset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">virtual_addr&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">page_size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&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">got&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g_pagemap_fd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">offset&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 读 8 个字节
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">got&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">8&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">// Check the &amp;#34;page present&amp;#34; flag.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1ULL&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">63&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="kt">uint64_t&lt;/span> &lt;span class="n">frame_num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">frame_number_from_pagemap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">frame_num&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">page_size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">virtual_addr&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">page_size&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&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;/div>&lt;h3 id="get_cache_slice-in_same_cache_set">get_cache_slice, in_same_cache_set&lt;/h3>
&lt;p>哈希相关缓存位，计算物理地址对应的 cache slice&lt;/p>
&lt;div class="highlight">&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="kt">int&lt;/span> &lt;span class="nf">get_cache_slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">phys_addr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">bad_bit&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">static&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">bits&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="mi">17&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">18&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">22&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">26&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">27&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">30&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="kt">int&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&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">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&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">hash&lt;/span> &lt;span class="o">^=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">phys_addr&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">bits&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">&amp;amp;&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="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">bad_bit&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&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">hash&lt;/span> &lt;span class="o">^=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">phys_addr&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">bad_bit&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">hash&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;/code>&lt;/pre>&lt;/div>&lt;p>检查两个物理地址是否属于相同的 cache set，对比低 17 位是否相等 &amp;amp;&amp;amp; 所处的 cache slice 是否一样&lt;/p>
&lt;div class="highlight">&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="kt">bool&lt;/span> &lt;span class="nf">in_same_cache_set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">phys1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">phys2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">bad_bit&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="kt">uint64_t&lt;/span> &lt;span class="n">mask&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">17&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="k">return&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">phys1&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">phys2&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">get_cache_slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phys1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bad_bit&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">get_cache_slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phys2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bad_bit&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="time_access-timing">time_access, timing&lt;/h3>
&lt;div class="highlight">&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">// Execute a CPU memory barrier. This is an attempt to prevent memory
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// accesses from being reordered, in case reordering affects what gets
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// evicted from the cache. It&amp;#39;s also an attempt to ensure we&amp;#39;re
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// measuring the time for a single memory access.
&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">// However, this appears to be unnecessary on Sandy Bridge CPUs, since
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// we get the same shape graph without this. （这是为什么呢？）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">inline&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">mfence&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">asm&lt;/span> &lt;span class="k">volatile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;mfence&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Measure the time taken to access the given address, in nanoseconds.
&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="nf">time_access&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uintptr_t&lt;/span> &lt;span class="n">ptr&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">struct&lt;/span> &lt;span class="nc">timespec&lt;/span> &lt;span class="n">ts0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">clock_gettime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CLOCK_MONOTONIC&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">ts0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rc&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&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="n">g_dummy&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">volatile&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">ptr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mfence&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="k">struct&lt;/span> &lt;span class="nc">timespec&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">clock_gettime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CLOCK_MONOTONIC&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rc&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">tv_sec&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ts0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">tv_sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1000000000&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">tv_nsec&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">ts0&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">tv_nsec&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 合成秒&amp;amp;纳秒差
&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;/div>&lt;blockquote>
&lt;p>关于单调时钟 CLOCK_MONOTONIC：A nonsettable system-wide clock that represents monotonic time since—as described by POSIX—&amp;ldquo;some unspecified point in the past&amp;rdquo;. On Linux, that point corresponds to the number of seconds that the system has been running since it was booted.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;p>测量多个内存地址的访问时间。通过在给定地址集上进行多次内存访问，测量缓存是否被命中，以及时间的变化。&lt;br>
取到第一个物理地址之后，筛选所有和他在同一个 cache set 的物理地址。做 10 次测量，取中位数时间。&lt;/p>
&lt;div class="highlight">&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="kt">int&lt;/span> &lt;span class="nf">timing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">addr_count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">bad_bit&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">size_t&lt;/span> &lt;span class="n">size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 分配 16MB 内存
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">uintptr_t&lt;/span> &lt;span class="n">buf&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">uintptr_t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">mmap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">NULL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PROT_READ&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">PROT_WRITE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MAP_PRIVATE&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">MAP_ANONYMOUS&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">MAP_POPULATE&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 class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buf&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="n">uintptr_t&lt;/span> &lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">addr_count&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buf&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uintptr_t&lt;/span> &lt;span class="n">phys1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_physical_addr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&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="n">uintptr_t&lt;/span> &lt;span class="n">next_addr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buf&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">page_size&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uintptr_t&lt;/span> &lt;span class="n">end_addr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buf&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">found&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="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">found&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">addr_count&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">uintptr_t&lt;/span> &lt;span class="n">addr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">next_addr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">next_addr&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">page_size&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">phys2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_physical_addr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">addr&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">in_same_cache_set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phys1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phys2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bad_bit&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">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">found&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">addr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">found&lt;/span>&lt;span class="o">++&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;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">runs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">times&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">runs&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">run&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">run&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">runs&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">run&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">g_dummy&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">volatile&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mfence&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">addr_count&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&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="n">g_dummy&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">volatile&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&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="n">mfence&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">times&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time_access&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">addrs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]);&lt;/span> &lt;span class="c1">// 重新访问 addrs[0]
&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 class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">runs&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">median_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">times&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">runs&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">2&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="kt">int&lt;/span> &lt;span class="n">rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">munmap&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">buf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rc&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&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="k">return&lt;/span> &lt;span class="n">median_time&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;/code>&lt;/pre>&lt;/div>&lt;h2 id="todo">TODO&lt;/h2>
&lt;ul>
&lt;li>添加本机测试&lt;/li>
&lt;li>下一篇相关 &lt;a href="https://blog.stuffedcow.net/2013/01/ivb-cache-replacement/">博客&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;a href="https://ieeexplore.ieee.org/document/6547110?reload=true&amp;amp;arnumber=6547110">https://ieeexplore.ieee.org/document/6547110?reload=true&amp;amp;arnumber=6547110&lt;/a>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Address_space_layout_randomization">Address_space_layout_randomization&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>Intel 的 &lt;a href="https://en.wikipedia.org/wiki/Intel_QuickPath_Interconnect">QuickPath Interconnect&lt;/a>，其设计目标是替代之前的“前端总线”技术，以实现快速路径互连。QPI 是一种用于在高端多插槽系统中连接多个 CPU 的协议。后于 2017 年，在 Skylake-SP Xeon 平台上，QPI 被 Intel Ultra Path Interconnect（UPI）替代。&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>非常遗憾的是，我并没有找到 Intel Core i5-7200U 的相关说明。只找到了 i7 某些型号采用了 QPI 的文档 &lt;a href="https://www.intel.com/content/dam/develop/external/us/en/documents/performance-analysis-guide-181827.pdf">Performance Analysis Guide for Intel® Core™ i7 Processor and Intel® Xeon™ 5500 processors&lt;/a>，pg5 有配图，说明了不同 LLC 通过 QPI 的联系。（还找到一篇相关&lt;a href="https://community.intel.com/t5/Intel-Moderncode-for-Parallel/Core-to-Core-Communication-Latency-in-Skylake-Kaby-Lake/m-p/1061658">博客&lt;/a>，待阅读）&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>找到了一篇有关于 DIP 的 &lt;a href="https://www.cs.cmu.edu/afs/cs/academic/class/15740-f18/www/papers/isca07-qureshi-dip.pdf">论文&lt;/a> 以及 &lt;a href="https://www.eecg.utoronto.ca/~moshovos/000/lib/exe/fetch.php?media=wiki:aca2017:cache_insertion_policies.pptx">PPT&lt;/a>，还有 &lt;a href="https://medium.com/@arpitguptarag/adaptive-insertion-policies-for-high-performance-caching-a741c52f515c">博客&lt;/a>&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>vim basic</title><link>https://topology2333.github.io/blog/posts/some-vims/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/some-vims/</guid><description>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&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">| x | 删除当前光标所在字符 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| A | 直接在行末添加 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| d | 删除命令，后跟对象 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| dd | 删除整行 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ndd | 删除 n 行 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| U | 恢复改行状态/撤销恢复 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| CTRL-R | 重做撤销 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| p | 粘贴（从上方） |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| r | 替换单个字符 |
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&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">| nw | 向前移动 n 个单词，不包括它的第一个字母 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ne | 光标到当前计起的第 n 个单词末尾，包括最后一个字母 |
&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;/code>&lt;/pre>&lt;/div></description></item><item><title>zfs: couldn't save system state</title><link>https://topology2333.github.io/blog/posts/bad-bpool/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://topology2333.github.io/blog/posts/bad-bpool/</guid><description>&lt;p>bpool 空间不够了。&lt;/p>
&lt;p>在 reddit 上找到了解决方案，用 &lt;a href="https://github.com/Venomtek/zsysctl-manual-gc/">GitHub&lt;/a> 上的脚本成功解决。以下引用自这个 &lt;a href="https://www.reddit.com/r/linuxquestions/comments/13jdzn3/how_to_maintain_20_minimum_free_space_on_bpool/">reddit&lt;/a>。&lt;/p>
&lt;p>尽管 zfs 只是 storing the differences，但是每个 state 都需要大约 100MB 的新存储空间。
由于 zsys 的默认策略是保持至少 20 个状态，这将需要 20 * 100MB = 2GB 的存储空间。这意味着我们要使用整个 boot 分区来进行快照。
还有一个解决方案是参考这篇 &lt;a href="https://didrocks.fr/2020/06/04/zfs-focus-on-ubuntu-20.04-lts-zsys-state-collection/">博客&lt;/a>，方法是手动编辑 &lt;code>zsys.conf&lt;/code> 减少快照存储数量。&lt;/p></description></item></channel></rss>