σμ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 スキル›モジュール 3 · ロックフリーキュー›トピック 3.2·~55 分上級

ABA問題とタグ付きポインタ

スキル上の位置モジュール 3/4 · トピック 2/3
3.2 ABA問題とタグ付きポインタ
—ラボスコア— / 100合計XP—連続日数—

読了 6分コード例 4件

  1. 01比較交換によるスタック
  2. 02ABAスケジュール
  3. 03変更を数えるタグ
  4. 04これから作るもの

比較交換は「値はまだ読んだ値のままか」を問えますが、「変更されてまた元に戻ったか」は問えません。ロックフリーなスタックでは、この死角がABA問題です。スタックについての古い認識のまま交換が成功し、同じ値が2回返されます。スタックをかき回すスレッドに対するpop1回の3,021通りのスケジュールのうち、21通りが通常のスタックを壊し、タグ付きのスタックを壊すものは1つもありません。

01

比較交換によるスタック

ロックフリースタックは、ノードを共有辞書に持ちます。ノード i は値 val{i} と、下のノードへのリンク next{i} を持ち、head が先頭を指します(空なら-1)。pushでは、head を読み、新しいノードのリンクをそれに向け、head を読んだ値から新しいノードへ交換します。交換に失敗したら誰かが head を変えたのでやり直します。popでは、head を読み、先頭ノードのリンクと値を読み、head を読んだノードからそのリンクへ交換します。勝ったpopは値を popped に記録します。スタックの各操作はジェネレータで、スレッドは yield from で実行し、戻り値も受け取れます。

main.pyPython
def make_stack(values, tagged=False):    top = 0 if values else -1    shared = {"head": (top, 0) if tagged else top, "popped": ()}    for i, value in enumerate(values):        shared["val%d" % i] = value        shared["next%d" % i] = i + 1 if i + 1 < len(values) else -1    return shareddef push(shared, node, value, tagged=False):    shared["val%d" % node] = value    while True:        yield        old = shared["head"]        yield        shared["next%d" % node] = old[0] if tagged else old        yield        new = (node, old[1] + 1) if tagged else node        if compare_and_swap(shared, "head", old, new):            returndef pop(shared, tagged=False):    while True:        yield        old = shared["head"]        idx = old[0] if tagged else old        if idx == -1:            return None        yield        nxt = shared["next%d" % idx]        value = shared["val%d" % idx]        yield        new = (nxt, old[1] + 1) if tagged else nxt        if compare_and_swap(shared, "head", old, new):            shared["popped"] = shared["popped"] + (value,)            return valuedef alone(shared):    def thread():        for _ in range(3):            print("popped", (yield from pop(shared)))    return [thread()]final, alive = run_prefix(alone, [0] * 20, make_stack([10, 20]))print(final["head"], final["popped"])
popped 10
popped 20
popped None
-1 (10, 20)

単独では普通のスタックで、popは10、20、そして None を返し、head は -1 に戻ります。問題は、popの3回の読み出しと交換の間に別のスレッドが動いたときに何が起きるかです。

02

ABAスケジュール

罠はこうです。被害者スレッドがpopを始め、head = 0(ノードA)とリンク next0 = 1(ノードB)を読み、交換の前に止められます。次に、かき回すスレッドがAをpopし、Bをpopし、ノード0を新しい値99でpushし直すので、head は再び0になり、その下には何もありません。被害者が目を覚まします。比較交換が問うのは head がまだ0かどうかだけで、確かに0なので交換は成功し、もうスタックにないノード1を head にしてしまいます。スケジュール [0, 0]、かき回す側の9手番、そして被害者の順に追い、conserved で確かめます。取り出した値と残った値の合計は、入れた値とちょうど一致しなければなりません。

main.pyPython
def walk(shared, tagged=False):    values = []    idx = shared["head"][0] if tagged else shared["head"]    while idx != -1 and len(values) < 10:        values.append(shared["val%d" % idx])        idx = shared["next%d" % idx]    return valuesdef conserved(shared, expected, tagged=False):    return sorted(list(shared["popped"]) + walk(shared, tagged)) == sorted(expected)def aba_program(tagged=False):    def make_threads(shared):        def victim():            yield from pop(shared, tagged)        def churner():            yield from pop(shared, tagged)            yield from pop(shared, tagged)            yield from push(shared, 0, 99, tagged)        return [victim(), churner()]    return make_threadsprefix = [0, 0] + [1] * 9 + [0]final, alive = run_prefix(aba_program(), prefix, make_stack([10, 20]))print(final)print("still on the stack:", walk(final), "| conserved:", conserved(final, [10, 20, 99]))
{'head': 1, 'popped': (10, 20, 10), 'val0': 99, 'next0': -1, 'val1': 20, 'next1': -1}
still on the stack: [20] | conserved: False

被害者は、かき回す側がすでに取り出した10を返し(popped: (10, 20, 10))、head はノード1ですが値20も取り出し済みなので、20はスタックにも popped にもあり、新しい値99は失われています。conserved は False です。失われた更新、重複した値、存在すべきでないノード。すべて、比較した値に対しては「正しい」交換が引き起こしたものです。

定義ABA問題

値がAとして読まれ、Bに変わり、比較交換が確かめる前にAに戻る現象。交換はAを見て成功しますが、値の周りの状態は変わっています。ノードが解放されてそのアドレスが再利用されるときに起きます。

03

変更を数えるタグ

対策は、head に自分の履歴を持たせることです。タグ付きポインタ版では head は (ノード, タグ) の組で、pushやpopが成功するたびに (新しいノード, タグ + 1) に交換します。比較交換は両方のフィールドを比べるので、かき回す側が残した head (0, 3) は、被害者が読んだ (0, 0) とは別物です。被害者の交換は失敗し、リトライを1回数えて本当のスタックを読み直し、99をpopします。同じスケジュールでも、タグがあれば正しく終わります。

main.pyPython
final, alive = run_prefix(aba_program(True), prefix, make_stack([10, 20], True))print(final["head"], "victim alive:", alive == [0])final, alive = run_prefix(aba_program(True), prefix + [0] * 6, make_stack([10, 20], True))print(final["popped"], walk(final, True), "| conserved:", conserved(final, [10, 20, 99], True))
(0, 3) victim alive: True
(10, 20, 99) [] | conserved: True
実機は空きビットで同じことをする

64ビットポインタはすべてのビットを必要とすることはまれなので、タグを上位16ビットに置いたり、倍幅の比較交換でポインタとカウンタを一緒に交換したりできます。一周したタグは理論上は繰り返しうるので、もう1つの対策である安全なメモリ回収は、誰かがまだ読むかもしれないノードの再利用を防ぎます。これは後のトピックで扱います。

main.pyPython
values = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:2]]for tagged in (False, True):    outcomes = explore_all(aba_program(tagged), make_stack(values, tagged))    corrupt = sum(n for key, n in outcomes.items() if not conserved(dict(key), values + [99], tagged))    print("tagged" if tagged else "plain ", sum(outcomes.values()), "schedules,", corrupt, "corrupt")
plain  3021 schedules, 21 corrupt
tagged 3021 schedules, 0 corrupt
実データが示すものSPYの最初の2つの終値(9,369と9,305セント)のスタック、popとかき回すスレッド
スケジュール3,021各スタックで探索
通常のスタック21壊れたスケジュール:値の重複または欠落
タグ付きスタック0壊れたスケジュール

3,021通り中21通りは1%未満です。実機のストレステストは何日走っても1つも踏まず、本番で1回だけ失敗するかもしれません。探索器はそのすべてを列挙し、タグはそのすべてを取り除きます。

04

これから作るもの

ラボは講義の順に進みます。共有辞書の中のスタック、比較交換ループによるpushとpop(通常とタグ付き)、スタックの走査、保存則の検査、被害者とかき回すスレッドのABAプログラム、そしてSPYの終値2つでの両スタックの全スケジュール研究です。

  • 関数 · 問い前提
  • make_stack(values, tagged)共有スタックはどんな形か?単純な算術
  • push(shared, node, value, tagged)ノードはどう積むか?単純な算術
  • pop(shared, tagged)値はどう取り出すか?単純な算術
  • walk(shared, tagged)スタックには何があるか?単純な算術
  • conserved(shared, expected, tagged)失われたり重複したりしたものはあるか?walk
  • aba_program(tagged)ABAの罠はどんな形か?poppush
  • study(bars)タグは何通りを救うか?make_stackaba_programconserved

重要ポイント

  • 比較交換は値を比べるだけで履歴は見ない。 A、B、Aと変わった head は変わっていないように見え、変わったスタックに対して交換が成功します。
  • 3,021通り中21通りで十分。 1%未満のバグはストレステストでは現れず、探索器には必ず現れます。
  • タグはA、B、AをA0、B1、A2に変える。 head の組は交換が成功するたびに変わるので、古い交換は必ず失敗します。
クイックチェック
タグ付き head は (ノード, タグ) の組で、変更が成功するたびにタグが1増えます。被害者は (0, 0) を読みました。その後、かき回す側が3回の変更(pop、pop、push)に成功しました。いまの head は何で、被害者の交換は成功しますか。
答えを選んでください。

次のトピック:スタックには先頭が1つしかありませんが、キューには両端があり、それぞれに多くのスレッドが来ます。複数プロデューサ・複数コンシューマのキューでは、プロデューサとコンシューマが fetch-and-add でチケットを取り、各スロットが連番を持つ有界キューを作り、全スケジュールで探索器で確かめます。

ラボ:採点される内容

6タスク · 100点
  • 共有スタック10点
  • push15点
  • pop20点
  • 走査と検査20点
  • ABAプログラム10点
  • SPYの表での研究25点
エクスプローラー · lab-lf-07
▾ lab-lf-07
▾ data
…
bash · ブラウザ内
learner@quantcraft:~/lab-lf-07$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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