σμ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.4·~1 時間上級

NUMAの影響

スキル上の位置モジュール 3/3 · トピック 4/7
3.4 NUMAの影響
—ラボスコア— / 100合計XP—連続日数—

読了 7分コード例 3件

  1. 01距離のあるメモリ
  2. 02ページの置き場所は誰が決めるか
  3. 03ローカルとリモートのアクセスを数える
  4. 04これから作るもの

2ソケットのサーバーでは、同じ読み出しでも、ページがどちらのソケットにあるかで80 nsにも130 nsにもなります。ページがどこにあるかは、最初に触れられたときに決まります。この講義では2つのノードをモデル化し、1つのスレッドがデータを初期化すると仕事の半分がリモートになる様子を示し、各スレッドが自分の担当部分に先に触れることで直します。

01

距離のあるメモリ

大きなサーバーにはプロセッサのソケットが2つあり、各ソケットに専用のメモリモジュールがつながっています。コアは自分のソケットのメモリを直接読みます。もう一方のソケットのメモリを読むには、要求がソケット間のリンクを通るので時間がかかります。このようなマシンはNUMAです。各ソケットとそのメモリが1つのノードを作り、スレッドのノードはそのコアがあるノードです。このモデルでは、2つのノードにそれぞれ2スレッドがあり、スレッド0と1がノード0、スレッド2と3がノード1です。ローカルアクセスは80 ns、リモートアクセスは130 nsです。

main.pyPython
print([node_of_thread(thread, 2) for thread in range(4)])for thread_node in (0, 1):    print("thread on node", thread_node, [LOCAL_NS if page_node == thread_node else REMOTE_NS for page_node in (0, 1)])
[0, 0, 1, 1]
thread on node 0 [80.0, 130.0]
thread on node 1 [130.0, 80.0]

各行は1つのスレッドから見た値です。自分のノードのページには80 ns、もう一方のノードのページには130 nsを払います。差の50 nsは小さく見えますが、メモリに届くすべてのアクセスにかかります。

02

ページの置き場所は誰が決めるか

OSはプログラムにメモリをページ単位で与え、ページが最初に使われた瞬間にそのノードを選びます。既定の規則はファーストタッチで、最初に触れたスレッドのノードにページが置かれ、そこに留まります。別の方法は、ページをノードにインターリーブすることです。次のコードは、(スレッド, アドレス) の短いアクセス列に両方の規則を適用します。

main.pyPython
def first_touch_placement(trace, threads_per_node):    placement = {}    for thread, address in trace:        page = address // PAGE_BYTES        if page not in placement:            placement[page] = node_of_thread(thread, threads_per_node)    return placementdef interleaved_placement(trace, n_nodes):    return {page: page % n_nodes for page in {address // PAGE_BYTES for _, address in trace}}trace = [(0, 0), (0, 4096), (3, 8192), (1, 4100)]print(first_touch_placement(trace, 2))print(interleaved_placement(trace, 2))
{0: 0, 1: 0, 2: 1}
{0: 0, 1: 1, 2: 0}

ページ0と1は、ノード0にあるスレッド0が最初に触れたのでノード0に置かれます。ページ2はノード1のスレッド3が最初に触れたのでノード1に置かれます。その後のスレッド1によるページ1へのアクセスは、ページを動かしません。インターリーブは誰が何に触れるかを無視します。ページ1は、1を2で割った余りが1だというだけでノード1に置かれます。

03

ローカルとリモートのアクセスを数える

典型的並列ジョブは、表をスレッドごとのチャンクに分けます。各スレッドは自分のチャンクだけを読むので、理想的には、読むページがすべて自分のノードにあります。次の関数は、400レコードを4スレッドで読むこのアクセスパターンを作り、3つの配置について、ローカルとリモートのアクセス数と時間を数えます。1つ目の「single」では、1つのスレッドがデータをすべて初期化したので、ファーストタッチですべてのページがノード0に置かれました。2つ目では、担当者が自分のチャンクを初期化しました。3つ目では、ページがインターリーブされています。

main.pyPython
def access_costs(trace, placement, threads_per_node):    local = 0    remote = 0    for thread, address in trace:        if placement[address // PAGE_BYTES] == node_of_thread(thread, threads_per_node):            local += 1        else:            remote += 1    return {"local": local, "remote": remote, "ns": local * LOCAL_NS + remote * REMOTE_NS}def partition_trace(n_records, n_threads, record_bytes):    chunk = n_records // n_threads    return [(thread, record * record_bytes) for thread in range(n_threads) for record in range(thread * chunk, (thread + 1) * chunk)]work = partition_trace(400, 4, 512)single = first_touch_placement([(0, address) for _, address in work], 2)owners = first_touch_placement(work, 2)spread = interleaved_placement(work, 2)for name, placement in (("single", single), ("owners", owners), ("interleaved", spread)):    print(name, access_costs(work, placement, 2))
single {'local': 200, 'remote': 200, 'ns': 42000.0}
owners {'local': 400, 'remote': 0, 'ns': 32000.0}
interleaved {'local': 208, 'remote': 192, 'ns': 41600.0}
average access time = local share × 80 ns + remote share × 130 ns(1)アクセスの半分がリモートなら 0.5 × 80 + 0.5 × 130 = 105 ns(80 nsではなく)

1つのスレッドがすべてを初期化すると、アクセスの半分がリモートになり、400回の読み出しに42,000 nsかかります。担当者が自分のチャンクを初期化すると、リモートはなくなり32,000 nsで、まったく同じプログラムが約1.3倍速くなります。ここではインターリーブは役に立ちません。各スレッドは連続したチャンクを読むので、インターリーブしたページはそのデータの半分を誤ったノードに置いてしまいます。インターリーブは、どのスレッドも全体を読むデータのためのもので、全員に良い配置が存在しない場合に使います。

1つのスレッドで初期化するのがよくある間違い

ワーカーを起動する前に、メインスレッドが大きな配列を確保して埋めると、すべてのページの配置がメインスレッドのノードに決まってしまいます。プログラムは正しく、プロファイラにも異常は見えず、ソケット数とともに大きくなる遅さだけが残ります。各ワーカーに、自分が使う部分を初期化させましょう。

配置を制御するツール

Linuxでは、numactl --interleave=all がプログラムのページをノードに分散し、numactl --cpunodebind と --membind がスレッドとメモリをノードに固定し、numastat がローカルだったアクセスの数を示します。トレーディングシステムでは通常、各スレッドをコアに固定し、そのノードのメモリを与えます。

実データが示すものSPYの512バイトのレコード4,000件(500ページ)、2ノード上の4スレッド、ローカル80 ns、リモート130 ns
1スレッドで初期化420,000ns4,000回中リモートが2,000回
担当者が初期化320,000nsすべてのアクセスがローカル
インターリーブしたページ420,000nsデータの半分が誤ったノードに
担当者によるファーストタッチの効果1.31×同じプログラム、同じデータ
4,000回の読み出しの時間(マイクロ秒)
1スレッド420
担当者320
インターリーブ420
04

これから作るもの

ラボは講義の順に進みます。スレッドのノード、2つの配置規則、分割したジョブのアクセスパターン、ローカルとリモートのアクセスの集計、そしてSPYのレコードでの研究です。

  • 関数 · 問い前提
  • node_of_thread(thread, threads_per_node)スレッドはどのノードで動くか?整数除算
  • first_touch_placement(trace, threads_per_node)ファーストタッチは各ページをどこに置くか?node_of_thread
  • interleaved_placement(trace, n_nodes)インターリーブは?単純な算術
  • partition_trace(n_records, n_threads, record_bytes)各スレッドはどんなアクセスをするか?単純な算術
  • access_costs(trace, placement, threads_per_node)ローカルとリモートのアクセスは何回で、時間は?node_of_thread
  • study(bars)SPYのレコードで誤った配置はどれだけ高くつくか?partition_tracefirst_touch_placementaccess_costs

重要ポイント

  • メモリには距離がある。 マルチソケットのサーバーでは、他方のノードのページは80 nsではなく130 nsかかるので、配置は性能の一部です。
  • ファーストタッチが決める。 ページは最初に触れたスレッドの動くノードに置かれるので、メインスレッドがすべてを埋めると仕事の半分がリモートになります。
  • データを読み手に合わせる。 各ワーカーに、自分が読むチャンクを初期化させます。ここでは1.3倍速くなります。インターリーブは、全スレッドが全体を読むデータだけに使います。
クイックチェック
4つのスレッドがそれぞれ表の自分の4分の1だけを読みます。最適な配置はどれですか。
答えを選んでください。

次のトピック:バイトがどこにあるかは話の半分にすぎず、どう割り当てられるかも重要です。アロケータとメモリプールでは、プログラムが多数の小さなオブジェクトを要求し、アロケータがそれらを渡す順序と場所が、後の走査が触れるキャッシュラインの数を決めます。

ラボ:採点される内容

6タスク · 100点
  • スレッドのノード5点
  • ファーストタッチの配置20点
  • インターリーブの配置10点
  • 分割されたジョブ15点
  • ローカルとリモートのアクセス25点
  • SPYの表での研究25点
エクスプローラー · lab-cl-10
▾ lab-cl-10
▾ data
…
bash · ブラウザ内
learner@quantcraft:~/lab-cl-10$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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