σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy路线图学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
路线图学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy路线图学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
路线图学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Cache locality 学习路线›模块 3 · 整台机器›主题 3.7·~1小时 30分钟高级

综合项目:重排逐笔表并证明缺失减少

路线进度模块 3/3 · 主题 7/7
3.7 综合项目:重排逐笔表并证明缺失减少
—实验得分— / 100总 XP—连续天数—

阅读约 5 分钟3 个代码示例

  1. 01一张表,三项工作
  2. 02布局就是一组列分组
  3. 03计数、定价、证明
  4. 04你要实现什么

本技能教过的一切,在一张逐笔数据表里汇合。八列,三种以不同方式读取它的工作,三种内存布局。你要为每个值的地址建模,用自己造的缓存数出每项工作的缺失,用延迟模型算出代价,并证明哪种布局胜出。没有哪种布局赢下所有工作;混合工作负载由混合布局赢下,缺失比结构体数组少 1.5 倍。

01

一张表,三项工作

逐笔数据表有 8 列、每列 8 字节:时间戳、买价和卖价及其数量、成交价、成交量和一些标志位。一行正好 64 字节,等于一条缓存行。有三项工作读取这张表。扫描读取每一行的价格列,用来算移动平均。报价工作读取每行的买价、卖价和买量,用来构建订单簿。查找读取表中任选的 1,000 行的完整逐笔记录,就像调试器或回放所做的那样。第一个主题里内存有层次,第二个主题里有 64 字节的缓存行,第五个主题里有表的两种布局:结构体数组(同一行的值放在一起)和数组结构体(每列单独存放)。现在有了第三条路,混合布局:把热的报价列放在一起,把冷的列放在一起。

02

布局就是一组列分组

三种布局其实是同一个想法。布局是一组列分组。同一分组的值逐行存放,各分组依次相连。把所有列放进一个分组就是结构体数组;每列一个分组就是数组结构体;两个各含四列的分组就是我们的混合布局。所以一个函数就能给出任意布局中任意值的地址:跳过包含该列的分组之前的所有分组,再在其中前进 row * len(group) + position 个值。

main.pyPython
def address(groups, row, col, n_rows, elem_bytes=ELEM_BYTES):    base = 0    for group in groups:        if col in group:            return base + (row * len(group) + group.index(col)) * elem_bytes        base += n_rows * len(group) * elem_bytes1    raise ValueError("column %d is in no group" % col)print(address([[0, 1, 2]], 2, 1, 10))print(address([[0], [1], [2]], 2, 1, 10))print(address([[1, 2], [0]], 3, 0, 10))
56
96
184
  1. 1一个分组占 n_rows * len(group) * elem_bytes 字节,下一个分组紧接在它后面开始。

手算检查这三个结果。一个含三列的分组里,第 2 行第 1 列是第 2 × 3 + 1 = 7 个值,在第 56 字节。每列一个分组时,第 1 列从第 0 列的 10 个值之后的第 80 字节开始,第 2 行再往后 16 字节:96。混合布局 [[1, 2], [0]] 中,第 0 列的分组排第二,在 10 行每行 2 个值(160 字节)之后,第 3 行再往后 24 字节:184。

模型只需要地址

缓存从来看不到列和行,只看到地址。所以几行代码就能比较那些在 C++ 里需要不同结构体和不同循环的布局。

03

计数、定价、证明

一项工作就是它在访问的每一行上要读的那些列。它的访问轨迹是:按顺序对每一行给出这些列的地址;缓存把轨迹变成缺失;第一个主题的延迟模型再把缺失变成时间,命中 1 ns、缺失 80 ns。比较就是对各布局做循环,证明就是最小的那个数。在 4,000 条逐笔数据上跑本讲的三项工作,你会看到最佳布局取决于工作本身。

main.pyPython
def workload_trace(groups, n_rows, reads, rows):    return [address(groups, row, col, n_rows) for row in rows for col in reads]def layout_misses(groups, n_rows, reads, rows=None):    rows = range(n_rows) if rows is None else rows    return count_misses(workload_trace(groups, n_rows, reads, rows))def compare_layouts(layouts, n_rows, reads, rows=None):    return {name: layout_misses(groups, n_rows, reads, rows) for name, groups in layouts.items()}def best_layout(misses):    return min(misses, key=misses.get)def time_ns(accesses, misses, hit_ns=HIT_NS, miss_ns=MISS_NS):    return (accesses - misses) * hit_ns + misses * miss_nsprint(workload_trace([[0, 1]], 4, [1, 0], [2]))for job, reads in WORKLOADS.items():    rows = sampled_rows(4000) if job == "lookup" else None    result = compare_layouts(LAYOUTS, 4000, reads, rows)    print(job, result, "->", best_layout(result))
[40, 32]
scan {'aos': 4000, 'soa': 500, 'hybrid': 2000} -> soa
quote {'aos': 4000, 'soa': 1500, 'hybrid': 2000} -> soa
lookup {'aos': 1000, 'soa': 8000, 'hybrid': 2000} -> aos

扫描只要一列:数组结构体每条缓存行装 8 个价格(500 次缺失),而结构体数组每次加载的行有八分之七被浪费(4,000 次缺失)。点查找要一行的全部八个值:结构体数组只要一条缓存行(1,000 次缺失),数组结构体要八条(8,000 次)。报价读热分组四列中的三列,所以混合布局不错,但不是最好。选哪种布局取决于每项工作运行的频率,而这正是混合负载要衡量的。

重新布局之前先测量

在扫描上获胜的布局,可能在查找上输八倍。改动真实程序的数据结构之前,先写下有哪些工作在读它、频率各是多少,并像这里一样数出每种布局的缺失。

main.pyPython
def study(bars):    n_rows = min(int(bars.shape[0]), 4000)    misses = {}    accesses = 0    for job, reads in WORKLOADS.items():        rows = sampled_rows(n_rows) if job == "lookup" else None        misses[job] = compare_layouts(LAYOUTS, n_rows, reads, rows)        accesses += len(reads) * (len(rows) if rows is not None else n_rows)    mixed = {name: sum(misses[job][name] for job in WORKLOADS) for name in LAYOUTS}    best = best_layout(mixed)    return {"mixed": mixed, "mixed_best": best, "reduction": mixed["aos"] / mixed[best],            "ns": {name: time_ns(accesses, mixed[name]) for name in LAYOUTS}}out = study(load_bars())print(out["mixed"], "->", out["mixed_best"])print(round(out["reduction"], 2), "times fewer misses than the array of structs")print({name: round(ns / 1000) for name, ns in out["ns"].items()}, "microseconds")
{'aos': 9000, 'soa': 10000, 'hybrid': 6000} -> hybrid
1.5 times fewer misses than the array of structs
{'aos': 735, 'soa': 814, 'hybrid': 498} microseconds

合计起来,结构体数组缺失 9,000 次,数组结构体 10,000 次,混合布局 6,000 次。混合布局没有哪项工作赢得很漂亮,也没有哪项输得很难看:扫描和报价的代价是数组结构体的两倍,查找的代价是结构体数组的两倍,而混合负载偏爱那个从不糟糕的布局。在延迟模型里,这是 498 µs 对结构体数组的 735 µs:缺失少 1.5 倍,时间少三分之一。这就是结业项目要求的证明,而且只对这一组工作的搭配成立。

定义热列与冷列

热列是大多数工作会一起读的列(这里是买价、卖价和两个数量);冷列很少被读(时间戳、成交价、成交量、标志位)。把每一类放在一起,常见的工作只碰很少的缓存行,少见的工作也不会太贵。

真实数据怎么说4,000 条 8 列的逐笔数据,32 KB 8 路缓存,命中 1 ns,缺失 80 ns
价格扫描,数组结构体500次缺失(结构体数组:4,000)
查找 1,000 条,结构体数组1,000次缺失(数组结构体:8,000)
混合负载,混合布局6,000次缺失(其余两种为 9,000 和 10,000)
相对结构体数组的减少1.5×缺失减少的倍数(735 µs → 498 µs)
按布局的混合缺失数
数组结构体10000
结构体数组9000
混合布局6000
04

你要实现什么

实验沿着本讲走:任意布局中一个值的地址、一项工作的访问轨迹、一项工作在一种布局和在所有布局中的缺失、最佳布局、一项工作的时间,以及对逐笔表的研究和混合负载的证明。

  • 函数 · 问题依赖
  • address(groups, row, col, n_rows)这个值在内存的什么位置?简单算术
  • workload_trace(groups, n_rows, reads, rows)这项工作会碰到哪些地址?address
  • layout_misses(groups, n_rows, reads, rows)这种布局下缺失多少次?workload_tracecount_misses
  • compare_layouts(layouts, n_rows, reads, rows)每种布局呢?layout_misses
  • best_layout(misses)哪一个胜出?简单算术
  • time_ns(accesses, misses)时间上要花多少?简单算术
  • study(bars)哪种布局赢下混合工作?compare_layoutsbest_layouttime_ns

要点

  • 布局就是一组列分组。 一个分组是结构体数组,每列一个分组是数组结构体,介于两者之间的是混合布局;一个函数就能给出它们的地址。
  • 工作决定布局。 数组结构体在扫描上以 500 次缺失获胜,在查找上以 8,000 次缺失落败;结构体数组则正相反。
  • 用计数来证明。 数出真实工作搭配下的缺失,用延迟模型算出代价,选缺失最少的布局:这里是混合布局,6,000 次对 9,000 次。
快速检查
一项工作只读 4,000 行的价格列。在数组结构体里每个值占 8 字节,一条缓存行装 64 字节。它会碰到多少条缓存行?
请选择一个答案。

下一个技能:性能的内存部分到此完成。优化路线上接下来的技能转向处理器:在向量寄存器里对许多值同时执行同一个操作,以及在内核之间不加锁地共享数据。

实验:评分内容

7 个任务 · 100 分
  • 值的地址20 分
  • 工作的访问轨迹15 分
  • 工作的缺失10 分
  • 每种布局10 分
  • 最佳布局5 分
  • 工作的时间10 分
  • 基于 SPY 表的研究30 分
资源管理器 · lab-cl-13
▾ lab-cl-13
▾ data
…
bash · 浏览器内
learner@quantcraft:~/lab-cl-13$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-cl-13 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记