σμ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 2 · Disposição e percurso›Tópico 2.1·~55 minIntermediário

Array de structs contra struct de arrays

Posição no roteiroMódulo 2 de 3 · Tópico 1 de 2
2.1 Array de structs contra struct de arrays
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Uma tabela, duas disposições
  2. 02Uma varredura de uma coluna
  3. 03Ler linhas inteiras inverte o resultado
  4. 04Deixe as contagens escolherem
  5. 05O que você vai construir

Uma tabela de ticks pode estar na memória de duas maneiras, e a certa depende de como você a lê. Varrer uma coluna de uma tabela guardada linha a linha carrega cinco vezes a memória que você usa; ler linhas inteiras de colunas separadas desperdiça o mesmo ao contrário. Esta aula conta as faltas das duas disposições e deixa as contagens escolherem.

01

Uma tabela, duas disposições

A memória é uma longa fila de bytes, então uma tabela bidimensional precisa ser achatada. Há duas maneiras naturais. Num array de structs os cinco valores de um dia (abertura, máxima, mínima, fechamento, volume) são guardados juntos, depois os cinco do dia seguinte, e assim por diante. Num struct de arrays vêm primeiro todas as aberturas, depois todas as máximas, e assim por diante. Os valores e o programa são os mesmos, mas os endereços mudam.

O endereço de um valor decorre de sua linha e coluna. No AoS o número do valor é linha vezes o número de colunas mais a coluna. No SoA é coluna vezes o número de linhas mais a linha. Multiplique por 8 bytes para um float64. O código abaixo mostra os endereços dos fechamentos dos quatro primeiros dias numa tabela de 100 dias e 5 colunas.

main.pyPython
def address(layout, row, col, n_rows, n_cols, elem_bytes=8):    if layout == "aos":        return (row * n_cols + col) * elem_bytes1    return (col * n_rows + row) * elem_bytesclose = 3print("aos:", [address("aos", row, close, 100, 5) for row in range(4)])print("soa:", [address("soa", row, close, 100, 5) for row in range(4)])
aos: [24, 64, 104, 144]
soa: [2400, 2408, 2416, 2424]
  1. 1No AoS a linha seguinte está n_cols valores adiante, então dois fechamentos consecutivos ficam a 5 valores (40 bytes) um do outro.

No AoS os fechamentos de dias consecutivos ficam a 40 bytes uns dos outros. No SoA ficam a 8 bytes, um logo após o outro, que é exatamente a leitura em ordem do primeiro tópico desta habilidade.

02

Uma varredura de uma coluna

Tirar a média dos fechamentos lê uma coluna da tabela. Transformamos essa leitura numa lista de endereços, passamos pelo cache construído nos tópicos anteriores e contamos as faltas para 1000 dias. O último número que mostramos é a parte útil.

main.pyPython
def scan_column(layout, col, n_rows, n_cols):    return [address(layout, row, col, n_rows, n_cols) for row in range(n_rows)]for layout in ("aos", "soa"):    misses = count_misses(scan_column(layout, 3, 1000, 5))    print(layout, "misses:", misses, "- useful share:", round(1000 * 8 / (misses * 64), 3))
aos misses: 625 - useful share: 0.2
soa misses: 125 - useful share: 1.0
useful share = value bytes ÷ row bytes (while a row fits in one line)(1)no AoS uma varredura de coluna carrega linhas inteiras: 8 ÷ 40 = 20%; no SoA cada byte carregado é um fechamento: cerca de 100%

A varredura SoA falha 125 vezes para 1000 valores, uma falta a cada oito valores e nada desperdiçado. A varredura AoS falha 625 vezes, cinco vezes mais, e só 20% dos bytes carregados são fechamentos. Os outros 80% são aberturas, máximas, mínimas e volumes que a média nunca olha, e eles expulsam linhas úteis do cache.

03

Ler linhas inteiras inverte o resultado

Muitos programas de ticks fazem outra pergunta: para este tick, me dê o preço, o tamanho e o lado juntos. Isso lê todos os valores de uma linha. No AoS a linha inteira é um bloco de 40 bytes, que fica em uma ou duas linhas de cache. No SoA os cinco valores estão em cinco arrays diferentes, então cada linha abre cinco linhas de cache. O código lê uma linha a cada sete e conta as faltas.

main.pyPython
def scan_rows(layout, rows, n_rows, n_cols):    return [address(layout, row, col, n_rows, n_cols) for row in rows for col in range(n_cols)]rows = list(range(0, 1000, 7))for layout in ("aos", "soa"):    print(layout, "misses:", count_misses(scan_rows(layout, rows, 1000, 5)))
aos misses: 214
soa misses: 625

Agora o AoS ganha por um fator de três: 214 faltas contra 625. A disposição que era cinco vezes pior para a varredura de coluna é três vezes melhor para a leitura de linhas. Nenhuma disposição é a melhor: cada uma é a melhor para uma forma de ler.

Você já usa as duas disposições

Um array bidimensional do NumPy na ordem padrão é uma disposição AoS: cada linha é contígua. Um DataFrame do pandas guarda juntas as colunas de um mesmo tipo, o que é parecido com SoA, e é uma das razões de as operações por coluna serem rápidas.

04

Deixe as contagens escolherem

Um programa real faz os dois tipos de leitura, então a escolha depende da mistura. Para cada disposição some as faltas da carga: varreduras de coluna vezes faltas de coluna mais leituras de linhas vezes faltas de linhas. Vence a disposição de menor total, e um empate vai para o AoS. O código abaixo testa quatro misturas na mesma tabela.

main.pyPython
def compare(n_rows, n_cols, col, rows):    out = {}    for layout in ("aos", "soa"):        out[layout] = {            "column": count_misses(scan_column(layout, col, n_rows, n_cols)),            "rows": count_misses(scan_rows(layout, rows, n_rows, n_cols)),        }    return outdef best_layout(n_column_scans, n_row_reads, n_rows, n_cols, col, rows):    counts = compare(n_rows, n_cols, col, rows)    totals = {layout: n_column_scans * counts[layout]["column"] + n_row_reads * counts[layout]["rows"] for layout in counts}    return "soa" if totals["soa"] < totals["aos"] else "aos"rows = list(range(0, 1000, 7))for scans, reads in ((1, 4), (1, 2), (1, 1), (4, 1)):    print(scans, "column scans per", reads, "row reads:", best_layout(scans, reads, 1000, 5, 3, rows))
1 column scans per 4 row reads: aos
1 column scans per 2 row reads: aos
1 column scans per 1 row reads: soa
4 column scans per 1 row reads: soa
Escolha pelo padrão de acesso, não pelo hábito

Com uma varredura de coluna para cada duas leituras de linha vence o AoS; com uma varredura para cada leitura de linha já vence o SoA. O ponto de equilíbrio fica perto de meia varredura por leitura de linha, e a sua carga real decide de que lado você está. Conte as leituras que seu programa realmente faz antes de reorganizar uma tabela.

O que dizem os dados reaisBarras diárias do SPY, 5.405 linhas × 5 colunas, cache de 32 KB de 8 vias
Varredura dos fechamentos: SoA67799,8% dos bytes carregados usados
Varredura dos fechamentos: AoS3,37820% dos bytes carregados usados
Leitura de 772 linhas: AoS1,159uma linha a cada sete
Leitura de 772 linhas: SoA3,383cinco linhas abertas por linha
Faltas da varredura dos fechamentos e das leituras de linhas, por disposição
Coluna, SoA677
Coluna, AoS3378
Linhas, AoS1159
Linhas, SoA3383
05

O que você vai construir

O laboratório segue a aula: as duas disposições, os dois tipos de leitura, a comparação, a escolha para uma carga, a parte útil e o estudo sobre a tabela real do SPY.

  • Função · perguntaBaseia-se em
  • address(layout, row, col, n_rows, n_cols)Onde vive este valor?Aritmética simples
  • scan_column(layout, col, n_rows, n_cols)Quais endereços uma varredura de coluna lê?address
  • scan_rows(layout, rows, n_rows, n_cols)Quais endereços uma leitura de linhas toca?address
  • compare(n_rows, n_cols, col, rows)Quantas faltas cada leitura custa em cada disposição?scan_columnscan_rows
  • best_layout(...)Qual disposição serve a esta carga?compare
  • useful_fraction(layout, col, n_rows, n_cols)Quanto do que é carregado é usado?scan_column
  • study(bars)Qual disposição serve à tabela do SPY?comparebest_layout

Pontos-chave

  • Uma disposição é um conjunto de passos. O AoS faz da varredura de coluna um passo da largura da linha; o SoA faz um passo de um valor.
  • Conte os bytes úteis. Uma varredura de coluna de uma tabela AoS de 5 colunas usa só 20% do que carrega, e o resto expulsa linhas úteis.
  • Escolha pela carga. Multiplique cada tipo de leitura por suas faltas em cada disposição e escolha o menor total; o equilíbrio era de cerca de meia varredura por leitura de linha.
Verificação rápida
Uma tabela tem 4 colunas de valores de 8 bytes guardada como AoS. Uma varredura de uma coluna carrega linhas de 32 bytes, duas por linha de 64 bytes. Que parte dos bytes carregados é usada?
Escolha uma resposta.

Próximo tópico: a disposição decidiu o passo de uma tabela. Em Ordem de percurso e ladrilhamento de laços a tabela é uma matriz e você escolhe a ordem dos laços e depois os corta em ladrilhos para que um bloco da matriz fique no cache enquanto é usado.

Lab: o que é avaliado

7 tarefas · 100 pts
  • O endereço de um valor15 pts
  • Uma varredura de coluna10 pts
  • Ler linhas inteiras10 pts
  • As faltas das duas leituras20 pts
  • A disposição para uma carga15 pts
  • A parte útil15 pts
  • O estudo com a tabela do SPY15 pts
Explorador · lab-cl-5
▾ lab-cl-5
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-cl-5$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-cl-5 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