σμ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
  • 日本語
  • 中文
Lock-free スキル›モジュール 4 · テストとメモリ安全性›トピック 4.2·~1 時間 10 分エキスパート

ハザードポインタとエポックベースの回収

スキル上の位置モジュール 4/4 · トピック 2/3
4.2 ハザードポインタとエポックベースの回収
—ラボスコア— / 100合計XP—連続日数—

読了 6分コード例 3件

  1. 01すぐ解放する:危険なpop
  2. 02ハザードポインタ:宣言してから確認
  3. 03エポック:入った時刻を宣言する
  4. 04これから作るもの

ロックフリーのスタックはブロックしませんが、取り出したノードを単純に解放することはできません。別のスレッドが少し前にヘッドを読み、そのノードの next を読もうとしているかもしれないからです。解放されたメモリは再利用されたりマップ解除されたりするので、その読み出しはuse-after-freeです。古典的な対策は2つあります。ハザードポインタは、使っているノードをスレッドが宣言します。エポックは、いつ入ったかを宣言します。どちらも、切り離したノードは誰も保持できなくなってから解放します。

01

すぐ解放する:危険なpop

モデルはABAトピックのスタックです。スレッドはヘッドを読み、ノードを読み、1回のcompare-and-swapでヘッドを入れ替えます。危険なpopは、入れ替えが成功した同じターンでノードを解放します。解放されたノードには誰も触れてはならず、read_node はスレッドがそれを読むたびにuse-after-freeを記録します。1回ずつpopする2スレッドのスケジュールはわずか20通りで、探索器は悪いものをすぐ見つけます。一方のスレッドがヘッドを読み、もう一方がそのノードをpopして解放し、最初のスレッドがそのあとで読むスケジュールです。

main.pyPython
def pop_unsafe(shared, tid):    while True:        yield        idx = shared["head"]        if idx == -1:            return None        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            free_node(shared, idx)            return valuedef two_pops(shared):    return [pop_unsafe(shared, 0), pop_unsafe(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])print(sum(outcomes.values()), "schedules,", hits, "of them read a freed node")
20 schedules, 6 of them read a freed node
02

ハザードポインタ:宣言してから確認

ハザードポインタは、解放を対話に変えます。スレッドはノードを読む前に、その番号を自分の枠に書きます。それだけでは足りません。ヘッドを読んでから枠に書くまでの間にノードがpopされ解放されているかもしれないので、スレッドは検証します。ヘッドをもう一度読み、動いていたらやり直します。popが成功しても、スレッドはノードを解放せず、自分の退役リストに入れてスキャンします。全スレッドの枠を1回読み、どの枠にも名前がない退役ノードだけを解放します。リストに残ったノードは次回のスキャンを待ちます。

main.pyPython
def pop_hazard(shared, tid):    while True:        yield        idx = shared["head"]        if idx == -1:            shared["hp%d" % tid] = -1            return None        yield        shared["hp%d" % tid] = idx        yield        if shared["head"] != idx:            continue        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            shared["hp%d" % tid] = -1            shared["retired%d" % tid] = shared["retired%d" % tid] + (idx,)            break    yield    hazards = tuple(shared["hp%d" % t] for t in range(shared["threads"]))    yield    keep = []    for node in shared["retired%d" % tid]:        if node in hazards:            keep.append(node)        else:            free_node(shared, node)    shared["retired%d" % tid] = tuple(keep)    return valuedef two_pops(shared):    return [pop_hazard(shared, 0), pop_hazard(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
12122 schedules, 0 read a freed node, up to 1 node waiting at the end
定義ハザードポインタ

スレッドごとの、読んでいる1つのノードの宣言。読む前に書き、読んだ後に検証します。退役ノードは、どの宣言にも名前がないときだけ解放されます。

03

エポック:入った時刻を宣言する

エポックベースの回収は宣言する量がずっと少なくなります。グローバルなエポックカウンタが1つあります。スレッドは操作に入るときに現在のエポックを自分の枠に書き、出るときに-1を書きます。個々のノードは宣言しません。エポック e で退役したノードを保持できるのはエポック e 以前に入ったスレッドだけなので、操作中の全スレッドが e より大きいエポックを宣言していれば解放して安全です。代償は、長く中に留まるスレッドが入って以降に退役した全ノードを引き止めることです。ハザードポインタが引き止めるのは名指しした1ノードだけです。

main.pyPython
def pop_epoch(shared, tid):    yield    shared["ep%d" % tid] = shared["epoch"]    while True:        yield        idx = shared["head"]        if idx == -1:            shared["ep%d" % tid] = -1            return None        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            shared["ep%d" % tid] = -1            shared["retired%d" % tid] = shared["retired%d" % tid] + ((idx, shared["epoch"]),)            shared["epoch"] = shared["epoch"] + 1            break    announced = []    for t in range(shared["threads"]):        yield        if shared["ep%d" % t] != -1:            announced.append(shared["ep%d" % t])    yield    keep = []    for node, epoch in shared["retired%d" % tid]:        if announced and epoch >= min(announced):            keep.append((node, epoch))        else:            free_node(shared, node)    shared["retired%d" % tid] = tuple(keep)    return valuedef two_pops(shared):    return [pop_epoch(shared, 0), pop_epoch(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
9012 schedules, 0 read a freed node, up to 1 node waiting at the end
リークはバグではないが、止まったスレッドは問題

どちらの方式も、スケジュールによっては最後に退役ノードが待ったまま残ります。探索器はそれを pending として報告します。後のスキャンで解放されるので何も失われません。エポックの本当の危険は、操作の途中で止まったスレッドです。新しいエポックを宣言しないので、その後に退役したノードは決して解放されず、メモリが増え続けます。止まったスレッドが保持するのは1ノードだけなので、ハザードポインタは影響を受けません。

実データが示すものSPYの最初の2つの終値(9,369と9,305セント)のスタックから1回ずつpopする2スレッド
すぐ解放206通りが解放済みノードを読む
ハザードポインタ12,122解放済みノードを読むものはなし
エポック9,012解放済みノードを読むものはなし
待ちのまま残るノード1安全な2方式とも最大

安全にはスケジュールの数という代償があります。宣言、検証、スキャンのどれもが、もう一方のスレッドが割り込める新しいステップだからです。20通りが12,122通りと9,012通りになりました。だからこそここで探索器が役に立ちます。方式がなぜ正しいかを知る必要はなく、そのすべてのスケジュールを確かめるだけです。

04

これから作るもの

ラボは講義の順に進みます。スレッドごとの枠を持つ共有状態、危険なpop、ハザードポインタのpop、エポックのpop、2つの不変条件(use-after-freeなし、欠損なし)の検査、方式の全スケジュールを探索する検証器、そしてSPYの終値での3方式の研究です。

  • 関数 · 問い前提
  • make_heap(values, threads)何が共有されるか?単純な算術
  • pop_unsafe(shared, tid)popして即解放単純な算術
  • pop_hazard(shared, tid)宣言、検証、退役、スキャン単純な算術
  • pop_epoch(shared, tid)入り、エポック付きで退役、回収単純な算術
  • violations(final)どの不変条件が壊れたか?単純な算術
  • verify(scheme, values, pops)方式は全スケジュールで安全か?explore_allviolations
  • study(bars)SPYのスタックで生き残る方式は?verify

重要ポイント

  • 解放はプロトコルである。 ノードを切り離しても解放が安全になるわけではありません。誰かがまだ読もうとしているかもしれません。
  • ハザードポインタはノードを、エポックは時刻を名指す。 ハザードポインタは宣言のあとに検証が必要です。エポックは安価ですが、止まったスレッドがすべてを引き止めます。
  • 穴は探索器に見つけさせる。 検証を外すと、12,122通りの一部が解放済みメモリを読みます。
クイックチェック
スレッドAはヘッド(ノード0)を読みましたが、まだ宣言していません。スレッドBがノード0をpopしてスキャンします。スレッドAはノード0を宣言した後、何をすべきですか。
答えを選んでください。

次のトピック:これはスタックで、おもちゃのようなものでした。最終プロジェクトロックフリーなマーケットデータのファンアウトで、スキルの全体を使います。SPYのティックが1つのプロデューサから複数のコンシューマへロックフリーの構造を通って流れ、探索器がその不変条件をすべてのスケジュールで証明します。

ラボ:採点される内容

7タスク · 100点
  • 共有状態5点
  • すぐ解放10点
  • ハザードポインタ20点
  • エポック20点
  • 不変条件10点
  • 検証器15点
  • SPYの表での研究20点
エクスプローラー · lab-lf-10
▾ lab-lf-10
▾ data
…
bash · ブラウザ内
learner@quantcraft:~/lab-lf-10$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-lf-10 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ