σμ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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • 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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Cache locality›Módulo 3 · La máquina en conjunto›Tema 3.4·~1 hAvanzado

Efectos NUMA

Posición en la rutaMódulo 3 de 3 · Tema 4 de 7
3.4 Efectos NUMA
—Puntuación del lab— / 100XP total—Racha—

5 min de lectura3 ejemplos de código

  1. 01Una memoria con distancia
  2. 02Quién decide dónde vive una página
  3. 03Contar los accesos locales y remotos
  4. 04Lo que va a construir

En un servidor de dos zócalos la misma lectura puede costar 80 ns o 130 ns, según el zócalo donde viva la página. Dónde vive una página se decide la primera vez que se toca. Esta lección modela dos nodos, muestra cómo un solo hilo que inicializa los datos hace remota la mitad del trabajo y lo arregla dejando que cada hilo toque primero su propia parte.

01

Una memoria con distancia

Un servidor grande tiene dos zócalos de procesador y cada zócalo tiene sus propios módulos de memoria. Un núcleo lee directamente la memoria de su zócalo. Para leer la del otro zócalo la petición cruza un enlace entre zócalos, lo que tarda más. Una máquina así es NUMA. Cada zócalo y su memoria forman un nodo, y el nodo de un hilo es donde está su núcleo. En nuestro modelo hay dos nodos con dos hilos cada uno: los hilos 0 y 1 en el nodo 0 y los hilos 2 y 3 en el nodo 1. Un acceso local cuesta 80 ns y uno remoto 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]

Cada fila es la vista de un hilo: paga 80 ns por las páginas de su propio nodo y 130 ns por las del otro. La diferencia, 50 ns, parece pequeña, pero se aplica a cada acceso que llega a la memoria.

02

Quién decide dónde vive una página

El sistema operativo da memoria a un programa en páginas y elige el nodo de una página en el momento en que se usa por primera vez. La regla por defecto es la primera toque: la página va al nodo del hilo que la toca primero y se queda allí. Una alternativa es intercalar las páginas entre los nodos. El código siguiente aplica ambas reglas a una lista corta de accesos escritos como (hilo, dirección).

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}

Las páginas 0 y 1 las tocó primero el hilo 0, que está en el nodo 0, así que van al nodo 0. La página 2 la tocó primero el hilo 3, en el nodo 1, así que va al nodo 1. El acceso posterior del hilo 1 a la página 1 no la mueve. El intercalado ignora quién toca qué: la página 1 va al nodo 1 simplemente porque 1 módulo 2 es 1.

03

Contar los accesos locales y remotos

Un trabajo paralelo típico divide una tabla en trozos, uno por hilo. Cada hilo lee luego solo su trozo, así que lo ideal es que cada página que lee esté en su propio nodo. Las funciones siguientes construyen ese patrón de acceso para 400 registros entre cuatro hilos, y cuentan los accesos locales y remotos y su tiempo para tres colocaciones. En la primera, «single», un solo hilo inicializó todos los datos, así que la primera toque puso cada página en el nodo 0. En la segunda, los propietarios inicializaron sus propios trozos. En la tercera, las páginas están intercaladas.

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)con la mitad de los accesos remotos: 0,5 × 80 + 0,5 × 130 = 105 ns en vez de 80 ns

Con un solo hilo que inicializa todo, la mitad de los accesos son remotos y el trabajo tarda 42 000 ns para 400 lecturas. Cuando los propietarios inicializan sus trozos, nada es remoto y tarda 32 000 ns, unas 1,3 veces más rápido, para exactamente el mismo programa. El intercalado no ayuda aquí: como cada hilo lee un trozo contiguo, las páginas intercaladas ponen la mitad de sus datos en el nodo equivocado. El intercalado sirve para datos que todos los hilos leen por todas partes, donde ninguna colocación puede ser buena para todos.

Inicializar en un solo hilo es el error habitual

Un hilo principal que reserva y llena un arreglo grande antes de lanzar los trabajadores decide la colocación de cada página, en su propio nodo. El programa es correcto y el perfilador no muestra nada raro, solo una ralentización que crece con el número de zócalos. Deje que cada trabajador inicialice la parte que va a usar.

Herramientas que controlan la colocación

En Linux, numactl --interleave=all reparte las páginas de un programa entre los nodos, numactl --cpunodebind y --membind fijan hilos y memoria a nodos, y numastat muestra cuántos accesos fueron locales. Los sistemas de trading suelen fijar cada hilo a un núcleo y darle memoria de su propio nodo.

Qué dicen los datos reales4000 registros de SPY de 512 bytes (500 páginas), 4 hilos en 2 nodos, 80 ns local, 130 ns remoto
Inicializado por un solo hilo420,000ns2000 accesos remotos de 4000
Inicializado por los propietarios320,000nstodos los accesos son locales
Páginas intercaladas420,000nsla mitad de los datos en el nodo equivocado
Ganancia de la primera toque por los propietarios1.31×el mismo programa, los mismos datos
Tiempo de las 4000 lecturas, en microsegundos
Un hilo420
Propietarios320
Intercalado420
04

Lo que va a construir

El laboratorio sigue la lección: el nodo de un hilo, las dos reglas de colocación, el patrón de acceso de un trabajo particionado, el recuento de accesos locales y remotos y el estudio sobre los registros de SPY.

  • Función · preguntaSe apoya en
  • node_of_thread(thread, threads_per_node)¿En qué nodo corre un hilo?La división entera
  • first_touch_placement(trace, threads_per_node)¿Dónde pone la primera toque cada página?node_of_thread
  • interleaved_placement(trace, n_nodes)¿Y el intercalado?Aritmética simple
  • partition_trace(n_records, n_threads, record_bytes)¿Qué accesos hace cada hilo?Aritmética simple
  • access_costs(trace, placement, threads_per_node)¿Cuántos accesos locales y remotos, y cuánto tiempo?node_of_thread
  • study(bars)¿Cuánto cuesta una mala colocación en los registros de SPY?partition_tracefirst_touch_placementaccess_costs

Ideas clave

  • La memoria tiene distancia. En un servidor multizócalo una página del otro nodo cuesta 130 ns en vez de 80 ns, así que la colocación forma parte del rendimiento.
  • La primera toque decide. Una página vive donde corre quien la toca primero; un hilo principal que lo llena todo deja la mitad del trabajo remoto.
  • Haga corresponder los datos con su lector. Deje que cada trabajador inicialice el trozo que lee: 1,3 veces más rápido aquí; intercale solo los datos que todos los hilos leen por todas partes.
Comprobación rápida
Cada uno de cuatro hilos lee solo su propio cuarto de una tabla. ¿Qué colocación es la mejor?
Elige una respuesta.

Siguiente tema: dónde viven los bytes es solo la mitad de la historia; cómo se reparten también importa. En Asignadores y pools de memoria un programa pide muchos objetos pequeños, y el orden y los lugares en que el asignador los reparte deciden cuántas líneas de caché toca un recorrido posterior.

Lab: qué se califica

6 tareas · 100 pts
  • El nodo de un hilo5 pts
  • Colocación por primera toque20 pts
  • Colocación intercalada10 pts
  • Un trabajo particionado15 pts
  • Accesos locales y remotos25 pts
  • El estudio con la tabla de SPY25 pts
Explorador · lab-cl-10
▾ lab-cl-10
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-cl-10$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-cl-10 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas