σμ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 AcademyRoadmapsRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
RoadmapsRoteiros
AO
+1,2 mil alunos
Entrar
  • 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 AcademyRoadmapsRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
RoadmapsRoteiros
AO
+1,2 mil alunos
Entrar
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Roteiro de Cache locality›Módulo 3 · A máquina como um todo›Tópico 3.5·~1 hAvançado

Alocadores e pools de memória

Posição no roteiroMódulo 3 de 3 · Tópico 5 de 7
3.5 Alocadores e pools de memória
—Nota do lab— / 100XP total—Sequência—

5 min de leitura3 exemplos de código

  1. 01De onde vêm os endereços?
  2. 02Objetos temporários espalham seus nós
  3. 03O que você vai construir

Cada nó de lista, nó de árvore ou ordem que um programa cria vem de um alocador, e o alocador decide que endereços eles recebem. Um malloc de uso geral espalha os objetos pequenos com cabeçalhos e com os próprios objetos temporários do programa; um pool os empacota. Esta aula modela os dois e percorre a mesma lista com 3,3 vezes menos faltas de cache a partir de um pool.

01

De onde vêm os endereços?

Quando um programa pede memória para um objeto, um alocador lhe dá um bloco e devolve o endereço. Um alocador de uso geral precisa lembrar o tamanho de cada bloco, então guarda um pequeno cabeçalho na frente dele, e arredonda os blocos para um múltiplo de 16 bytes para que qualquer tipo de valor caiba. O modelo mais simples nunca reutiliza memória: cada pedido toma um bloco logo depois do anterior e o programa recebe o endereço logo depois do cabeçalho.

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. 1O bloco é o pedido mais o cabeçalho, arredondado para um múltiplo de 16: 24 bytes precisam de um bloco de 48 bytes, 100 bytes precisam de 128.

Um nó de 24 bytes (um fechamento, um volume e um ponteiro seguinte, 8 bytes cada) recebe um bloco de 48 bytes, o dobro do que precisa, e um objeto temporário de 100 bytes recebe 128. Os três endereços são 16, 64 e 192: o segundo pedido começa depois do bloco de 48 bytes do primeiro, o terceiro depois do bloco de 128 bytes do segundo.

02

Objetos temporários espalham seus nós

O código real não constrói uma lista no vácuo. Entre dois nós ele formata uma mensagem, monta uma chave ou analisa uma linha, e esses objetos temporários também são alocados. Com um alocador simples que nunca reutiliza memória, eles ficam entre os nós. A primeira função abaixo monta o plano de pedidos, um nó seguido de três temporários de 100 bytes, e a segunda atende o plano e guarda só os endereços dos nós. Para comparar, escrevemos também um pool, que entrega vagas fixas uma logo após a outra.

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]

Com três temporários por nó os nós ficam a 432 bytes um do outro: 48 do nó e 3 × 128 do lixo. Sem temporários ficam a 48 bytes. Num pool ficam a 24 bytes, exatamente o tamanho de um nó, sem cabeçalho nem lixo. Uma linha de 64 bytes guarda um nó disperso, 1,3 nó só com cabeçalhos e 2,7 nós no pool. Agora percorremos 3000 nós, lendo o primeiro e o último byte de cada um, pelo cache dos tópicos anteriores.

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

A lista espalhada custa 3750 faltas e se estende por 1265 KB. O pool, que guarda exatamente os mesmos 3000 nós, custa 1125 faltas em 70 KB: 3,3 vezes menos, e os nós caberiam até num cache L2. O programa, os dados e a ordem do percurso são os mesmos; só os endereços mudaram.

DefiniçãoPool e arena

Um pool entrega vagas de tamanho igual de um único bloco contíguo e as retoma numa lista livre. Uma arena só move um ponteiro para a frente, entrega qualquer tamanho sem cabeçalho e libera tudo de uma vez. Os dois mantêm lado a lado os objetos usados juntos.

A mesma história em Python

Uma lista Python de 3000 floats guarda ponteiros para 3000 objetos float separados, espalhados pelo alocador, enquanto um array NumPy guarda os 3000 valores lado a lado. Percorrer o array toca algumas centenas de linhas; percorrer a lista toca milhares. É mais um motivo para o NumPy ser rápido.

Pools trocam flexibilidade por localidade

Um pool precisa de objetos de um só tamanho e de uma boa estimativa de quantos serão, e uma arena não pode liberar um único objeto antes. Use-os para as estruturas quentes de um programa, como um livro de ordens ou uma lista de ticks, e deixe o resto para o alocador de uso geral.

O que dizem os dados reaisuma lista de 3000 dias do SPY, nós de 24 bytes, cache de 32 KB de 8 vias
Espalhada por temporários3,750faltas, espalhadas por 1.265 KB
Só os cabeçalhos do malloc2,250faltas, espalhadas por 140 KB
Pool1,125faltas, empacotadas em 70 KB
Ganho do pool3.3×menos faltas para o mesmo percurso
Faltas de percorrer a lista, por alocador
Espalhada3750
Só cabeçalhos2250
Pool1125
03

O que você vai construir

O laboratório segue a aula: o arredondamento, o alocador de uso geral simples, o pool, o plano de pedidos com temporários, os endereços dos nós, a contagem de faltas de um percurso e o estudo sobre uma lista de dias do SPY.

  • Função · perguntaBaseia-se em
  • round_up(n, align)Quanto é n arredondado para um múltiplo?Aritmética simples
  • malloc_addresses(sizes)Onde um alocador de uso geral põe cada bloco?round_up
  • pool_addresses(n, slot_bytes)E um pool?Aritmética simples
  • alloc_plan(...)Que pedidos o programa faz?Aritmética simples
  • node_addresses(...)Onde os nós vão parar?alloc_planmalloc_addresses
  • traversal_misses(addresses, node_bytes)Quantas faltas um percurso custa?Cache
  • study(bars)Quanto um pool economiza na lista do SPY?node_addressespool_addressestraversal_misses

Pontos-chave

  • O alocador escolhe os endereços. Cabeçalhos e arredondamento dobram um nó de 24 bytes para 48 bytes, e os temporários alocados no meio afastam os nós 432 bytes.
  • Nós espalhados custam faltas. Percorrer os mesmos 3000 nós custou 3750 faltas espalhados e 1125 a partir de um pool.
  • Agrupe os objetos quentes. Pools e arenas mantêm lado a lado os objetos usados juntos; use-os para as poucas estruturas quentes e deixe o resto para o malloc.
Verificação rápida
Nós de 24 bytes estão um após o outro num pool. Quantas linhas de 64 bytes, no mínimo, a leitura de 3000 deles toca?
Escolha uma resposta.

Próximo tópico: até agora cada custo vinha da memória. O último efeito de hardware desta habilidade vem dos palpites do processador. Em Previsão de desvios você modela um preditor, conta seus erros num laço com um if dentro e vê por que o mesmo laço é mais rápido com dados ordenados.

Lab: o que é avaliado

7 tarefas · 100 pts
  • Arredondar para cima5 pts
  • Um alocador de uso geral simples20 pts
  • Um pool10 pts
  • Os pedidos de um programa15 pts
  • Os endereços dos nós10 pts
  • O custo de um percurso15 pts
  • O estudo com a tabela do SPY25 pts
Explorador · lab-cl-11
▾ lab-cl-11
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-cl-11$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-cl-11 volta aos arquivos iniciais e o terminal é limpo. Sua solução atual será perdida — isso não pode ser desfeito.

Será redefinido
  • main.py → código inicial
  • Histórico do terminal e saída dos testes
  • Alterações não enviadas
Será mantido
  • Melhor nota do lab
  • Seu XP e sua sequência
  • Progresso da aula e anotações