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

アロケータとメモリプール

スキル上の位置モジュール 3/3 · トピック 5/7
3.5 アロケータとメモリプール
—ラボスコア— / 100合計XP—連続日数—

読了 6分コード例 3件

  1. 01アドレスはどこから来るのか
  2. 02一時オブジェクトがノードを散らばらせる
  3. 03これから作るもの

プログラムが作るリストのノード、木のノード、注文はすべてアロケータから来ており、そのアドレスを決めるのもアロケータです。汎用の malloc は、ヘッダと、プログラム自身の一時オブジェクトのおかげで小さなオブジェクトを散らばらせ、プールは詰めて並べます。この講義では両方をモデル化し、同じリストをプールから走査するとキャッシュミスが3.3分の1になることを示します。

01

アドレスはどこから来るのか

プログラムがオブジェクト用のメモリを要求すると、アロケータがブロックを渡してアドレスを返します。汎用のアロケータは各ブロックの大きさを覚えておく必要があるので、ブロックの前に小さなヘッダを置き、どんな値でも入れられるようにブロックを16バイトの倍数に切り上げます。最も単純なモデルはメモリを再利用しません。各要求は直前のブロックのすぐ後ろにブロックを取り、プログラムはヘッダのすぐ後ろのアドレスを受け取ります。

main.pyPython
def round_up(n, align):    return -(-n // align) * aligndef malloc_addresses(sizes, header=HEADER_BYTES, align=ALIGN):    addresses = []    start = 0    for size in sizes:        addresses.append(start + header)1        start += round_up(size + header, align)    return addressesprint(malloc_addresses([24, 100, 24]))print([round_up(size + 16, 16) for size in (24, 100)])
[16, 64, 192]
[48, 128]
  1. 1ブロックは要求サイズ+ヘッダを16の倍数に切り上げたもので、24バイトには48バイト、100バイトには128バイトのブロックが必要です。

24バイトのノード(終値、出来高、次ポインタが各8バイト)は必要量の2倍の48バイトのブロックを受け取り、100バイトの一時オブジェクトは128バイトを受け取ります。3つのアドレスは16、64、192です。2つ目の要求は1つ目の48バイトのブロックの後ろ、3つ目は2つ目の128バイトのブロックの後ろから始まります。

02

一時オブジェクトがノードを散らばらせる

実際のコードは、リストを真空の中で作るわけではありません。2つのノードの間で、メッセージを整形したり、キーを作ったり、行を解析したりし、その一時オブジェクトも割り当てられます。メモリを再利用しない単純なアロケータでは、それらがノードの間に入り込みます。次の最初の関数は要求の計画(ノード1つに100バイトの一時オブジェクト3つ)を作り、2つ目はその計画を実行してノードのアドレスだけを残します。比較のため、固定スロットを次々に渡すプールも書きます。

main.pyPython
def alloc_plan(n_nodes, garbage_per_node, garbage_bytes, node_bytes):    sizes = []    node_index = []    for _ in range(n_nodes):        node_index.append(len(sizes))        sizes.append(node_bytes)        sizes.extend([garbage_bytes] * garbage_per_node)    return sizes, node_indexdef node_addresses(n_nodes, garbage_per_node, garbage_bytes, node_bytes):    sizes, node_index = alloc_plan(n_nodes, garbage_per_node, garbage_bytes, node_bytes)    addresses = malloc_addresses(sizes)    return [addresses[i] for i in node_index]def pool_addresses(n, slot_bytes, base=0):    return [base + i * slot_bytes for i in range(n)]print(node_addresses(3, 3, 100, 24))print(node_addresses(3, 0, 100, 24))print(pool_addresses(3, 24))
[16, 448, 880]
[16, 64, 112]
[0, 24, 48]

ノードあたり3つの一時オブジェクトがあると、ノードは432バイト離れます(ノード48、ゴミ3 × 128)。一時オブジェクトがなければ48バイト、プールでは、ヘッダもゴミもなくノードの大きさそのものの24バイトです。64バイトのラインには、散らばったノードなら1つ、ヘッダだけなら1.3個、プールなら2.7個が入ります。ここで3,000個のノードを、各ノードの最初と最後のバイトを読みながら、これまでのトピックのキャッシュで走査します。

main.pyPython
def traversal_misses(addresses, node_bytes):    cache = Cache()    for address in addresses:        cache.access(address)        cache.access(address + node_bytes - 1)    return cache.missesn = 3000for name, addresses in (("scattered", node_addresses(n, 3, 100, 24)), ("headers only", node_addresses(n, 0, 100, 24)), ("pool", pool_addresses(n, 24))):    print(name, traversal_misses(addresses, 24), "misses, last node at", addresses[-1] // 1024, "KB")
scattered 3750 misses, last node at 1265 KB
headers only 2250 misses, last node at 140 KB
pool 1125 misses, last node at 70 KB

散らばったリストは3,750回ミスし、1,265 KBに広がります。まったく同じ3,000個のノードを持つプールは、70 KBで1,125回のミスで、3.3分の1です。ノードはL2キャッシュにも収まるほどです。プログラム、データ、走査の順序は同じで、変わったのはアドレスだけです。

定義プールとアリーナ

プールは、連続した1つの領域から同じ大きさのスロットを渡し、フリーリストで取り戻します。アリーナはポインタを前に進めるだけで、ヘッダなしにどんな大きさでも渡し、すべてを一度に解放します。どちらも、一緒に使われるオブジェクトを隣り合わせに保ちます。

Pythonでも同じ話

3,000個のfloatを入れたPythonのリストは、アロケータが散らばらせた3,000個の別々のfloatオブジェクトへのポインタを持ちますが、NumPyの配列は3,000個の値を隣同士に保存します。配列の走査が触れるラインは数百、リストの走査は数千です。NumPyが速い理由がもう1つ増えました。

プールは柔軟性と引き換えに局所性を得る

プールは1種類の大きさのオブジェクトと、その数のおおよその見積もりを必要とし、アリーナは1つのオブジェクトだけを早く解放することができません。注文板やティックのリストのような、プログラムのホットな構造に使い、残りは汎用アロケータに任せましょう。

実データが示すものSPYの3,000日のリスト、24バイトのノード、32 KBの8ウェイキャッシュ
一時オブジェクトで散らばった3,750ミス、1,265 KBに分散
mallocのヘッダのみ2,250ミス、140 KBに分散
プール1,125ミス、70 KBに集約
プールの効果3.3×同じ走査でミスが減る倍率
リスト走査のミス数(アロケータ別)
散らばり3750
ヘッダのみ2250
プール1125
03

これから作るもの

ラボは講義の順に進みます。切り上げ、単純な汎用アロケータ、プール、一時オブジェクトを含む要求の計画、ノードのアドレス、走査のミス数、そしてSPYの日のリストでの研究です。

  • 関数 · 問い前提
  • round_up(n, align)n を倍数に切り上げると?単純な算術
  • malloc_addresses(sizes)汎用アロケータは各ブロックをどこに置くか?round_up
  • pool_addresses(n, slot_bytes)プールは?単純な算術
  • alloc_plan(...)プログラムはどんな要求をするか?単純な算術
  • node_addresses(...)ノードはどこに置かれるか?alloc_planmalloc_addresses
  • traversal_misses(addresses, node_bytes)走査は何回ミスするか?Cache
  • study(bars)SPYのリストでプールはどれだけ節約するか?node_addressespool_addressestraversal_misses

重要ポイント

  • アロケータがアドレスを選ぶ。 ヘッダと切り上げで24バイトのノードは48バイトになり、間に割り当てられる一時オブジェクトでノードは432バイト離れます。
  • 散らばったノードはミスを生む。 同じ3,000個のノードの走査は、散らばっていると3,750回、プールからだと1,125回ミスしました。
  • ホットなオブジェクトを詰める。 プールとアリーナは、一緒に使われるオブジェクトを隣り合わせに保ちます。少数のホットな構造に使い、残りはmallocに任せましょう。
クイックチェック
24バイトのノードがプールの中で連続して並んでいます。3,000個を読むと、少なくとも何本の64バイトのラインに触れますか。
答えを選んでください。

次のトピック:ここまでのコストはすべてメモリから来ていました。このスキルの最後のハードウェア効果は、プロセッサの予想から来ます。分岐予測では、予測器をモデル化し、if を含むループでその予測ミスを数えて、同じループがソート済みのデータで速くなる理由を見ます。

ラボ:採点される内容

7タスク · 100点
  • 切り上げ5点
  • 単純な汎用アロケータ20点
  • プール10点
  • プログラムの要求15点
  • ノードのアドレス10点
  • 走査のコスト15点
  • SPYの表での研究25点
エクスプローラー · lab-cl-11
▾ lab-cl-11
▾ data
…
bash · ブラウザ内
learner@quantcraft:~/lab-cl-11$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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