Pular para o conteúdo

Desempenho

O que é lento, por quê, e o que fazer a respeito.

O que esperar#

DataForge é um interpretador de árvore escrito em Python, sem otimização. Cada nó da AST é visitado por uma chamada de método. Isso o torna mais lento que Python — que já é lento comparado a linguagens compiladas.

Para scripts, ferramentas de terminal e processamento de alguns milhares de registros, isso não aparece. Para laços de milhões de iterações, aparece.

Meça antes de otimizar#

dataforge
adopt Arcane.Time as Time

medida := Time.measure(trabalho_pesado)
out $"{medida.result} em {round(medida.ms, 2)} ms"

A intuição sobre o que é lento erra com frequência. Uma medição isolada também é ruído — o mínimo de várias execuções é mais informativo que a média.

Escolher o algoritmo certo#

Vale mais que qualquer micro-otimização:

dataforge
action com_laco(n):          # O(n)
    total := 0
    cycle i from 1 to n:
        total += i
    yield total

action com_formula(n):       # O(1)
    yield n * (n + 1) ~/ 2

As duas dão o mesmo resultado. Para n = 100000, a segunda é milhares de vezes mais rápida.

Streams para dados grandes#

A diferença não é de velocidade, é de memória — e às vezes de trabalho evitado:

dataforge
# carrega tudo três vezes
linhas := IO.read("app.log").lines()
registros := linhas >> morph interpretar
erros := registros >> sift e: e["nivel"] is "ERROR"

# lê até o primeiro erro e para
apenas(interpretar(linhas_do_log()), "ERROR").first()

Veja Streams.

Índice em vez de busca linear#

dataforge
# O(n) a cada consulta
achado := pessoas >> sift p: p["id"] is 20

# O(n) uma vez, O(1) depois
por_id := {p["id"]: p cycle p in pessoas}
achado := por_id[20]

Banco: execute_many e índices#

dataforge
DB.execute_many(conn, "INSERT INTO t VALUES (?, ?)", lote)  # uma ida
DB.execute(conn, "CREATE INDEX idx_nome ON produtos(nome)")

Recursão tem limite#

O interpretador aborta em 1000 quadros com StackOverflowError_. Recursão sobre listas grandes deve virar >> distill ou um laço.

O que está no roadmap#

  • Cachear a resolução de nomes por nó da AST
  • Uma IR e uma VM de bytecode com máquina de pilha
  • Cache de compilação entre execuções

Nada disso é urgente enquanto ninguém tiver um caso real onde o desempenho impede o uso. Se você tiver, abra uma issue com o código — é a melhor forma de priorizar.