Guia de Desempenho

Este guia apresenta as características de desempenho do idecomp e estratégias práticas para otimizar fluxos de trabalho que processam grandes volumes de arquivos do DECOMP. As recomendações aqui descritas são especialmente relevantes para análises em lote, como a consolidação de resultados de múltiplos cenários ou revisões.

Importacao

Ao executar import idecomp ou from idecomp.decomp import AlgumaClasse, o interpretador Python carrega o módulo idecomp/__init__.py, que importa imediatamente o subpacote idecomp.decomp. O __init__.py desse subpacote, por sua vez, importa todas as 41 classes de arquivo do DECOMP de uma vez. Esse comportamento de importação antecipada (eager import) garante que a interface pública do pacote esteja sempre disponível de forma consistente, mas implica um custo de inicialização proporcional ao número de módulos carregados.

Para a maioria dos scripts interativos e notebooks, esse custo é imperceptível. Entretanto, em aplicações que chamam scripts Python repetidamente — por exemplo, pipelines de automação que invocam o interpretador a cada execução — o tempo de inicialização do processo pode ser relevante. Nesses casos, é possível reduzir o escopo da importação apontando diretamente para o módulo do arquivo desejado, em vez de importar pelo namespace agregado do subpacote.

# Importa pelo namespace agregado: carrega todos os 41 modulos
from idecomp.decomp import Dadger

# Importa diretamente do modulo: carrega apenas o modulo necessario
from idecomp.decomp.dadger import Dadger

A segunda forma acessa idecomp/decomp/dadger.py diretamente, sem acionar a importação em cadeia de todos os demais módulos do subpacote. O resultado funcional é idêntico — a classe Dadger obtida é a mesma — mas o tempo de importação é menor quando apenas um ou poucos arquivos precisam ser processados.

Para medir o impacto no seu ambiente, utilize o módulo time do Python antes de decidir qual estratégia adotar:

import time

inicio = time.perf_counter()
from idecomp.decomp.dadger import Dadger
fim = time.perf_counter()

print(f"Tempo de importacao: {(fim - inicio) * 1000:.2f} ms")

Leitura e Escrita de Arquivos

O padrão básico de leitura de um arquivo é sempre o mesmo: instanciar a classe correspondente chamando o método de classe read com o caminho do arquivo no disco. O objeto retornado mantém todos os dados do arquivo em memória e expõe as informações por meio de propriedades que retornam pd.DataFrame ou valores escalares tipados.

from idecomp.decomp import DecOperSist

# Leitura de um unico arquivo
arq = DecOperSist.read("./resultados/dec_oper_sist.csv")
df = arq.tabela
print(df.shape)

Em análises de resultados do DECOMP, é comum processar o mesmo arquivo de saída proveniente de múltiplos cenários ou revisões armazenados em diretórios separados. O padrão recomendado para esse caso é iterar sobre os caminhos, ler cada arquivo, extrair o DataFrame desejado e acumulá-los em uma lista para concatenação ao final:

from pathlib import Path
import pandas as pd
from idecomp.decomp.dec_oper_sist import DecOperSist

dfs = []
for caminho in Path("./resultados").glob("*/dec_oper_sist.csv"):
    arq = DecOperSist.read(str(caminho))
    dfs.append(arq.tabela)

df_total = pd.concat(dfs, ignore_index=True)
print(f"Total de linhas consolidadas: {len(df_total)}")

Concatenar ao final, em vez de concatenar incrementalmente dentro do loop, evita a criação de cópias intermediárias do DataFrame a cada iteração — um padrão de desempenho amplamente recomendado pelo próprio pandas. Quando o número de arquivos for muito grande, considere adicionar ao DataFrame uma coluna de identificação (nome do diretório ou do cenário) antes de acrescentá-lo à lista, para facilitar a rastreabilidade dos dados na análise subsequente.

Para medir o tempo total de um processamento em lote, envolva o loop com o módulo time:

import time
from pathlib import Path
import pandas as pd
from idecomp.decomp.dec_oper_sist import DecOperSist

caminhos = list(Path("./resultados").glob("*/dec_oper_sist.csv"))
inicio = time.perf_counter()

dfs = []
for caminho in caminhos:
    arq = DecOperSist.read(str(caminho))
    dfs.append(arq.tabela)

df_total = pd.concat(dfs, ignore_index=True)
fim = time.perf_counter()

print(f"{len(caminhos)} arquivos processados em {fim - inicio:.2f}s")

Uso de Memoria

Cada objeto de arquivo do idecomp mantém em memória a representação Python completa do arquivo lido — registros, blocos ou colunas CSV, dependendo do tipo. Para arquivos de saída CSV com muitas linhas (como dec_oper_sist.csv com colunas de estágio, cenário e patamar), o objeto pode ocupar memória significativa quando múltiplos arquivos são carregados simultaneamente.

O padrão recomendado para operações em lote é ler, extrair e descartar: após obter o DataFrame desejado de um objeto de arquivo, remova a referência ao objeto com del para que o coletor de lixo do Python possa liberar a memória associada.

from idecomp.decomp.dec_oper_sist import DecOperSist

arq = DecOperSist.read("./dec_oper_sist.csv")
df = arq.tabela         # extrai o DataFrame necessario
del arq                 # libera o objeto do arquivo da memoria

# A partir daqui, apenas df permanece em memoria
print(df.head())

Em loops que processam dezenas ou centenas de arquivos, combine esse padrão com uma chamada explícita ao coletor de lixo ao final de cada iteração quando o consumo de memória for crítico:

import gc
from pathlib import Path
import pandas as pd
from idecomp.decomp.dec_oper_sist import DecOperSist

dfs = []
for caminho in Path("./resultados").glob("*/dec_oper_sist.csv"):
    arq = DecOperSist.read(str(caminho))
    df_parcial = arq.tabela
    del arq        # descarta o objeto antes de passar para o proximo arquivo
    gc.collect()   # forca a coleta de lixo (recomendado para lotes muito grandes)
    dfs.append(df_parcial)

df_total = pd.concat(dfs, ignore_index=True)

Note que gc.collect() adiciona um pequeno overhead por chamada. Avalie se o ganho de memória justifica esse custo no seu caso de uso, medindo o consumo de memória com ferramentas como tracemalloc (biblioteca padrão) ou memory_profiler (pacote externo).

Dicas de Otimizacao

As dicas a seguir sintetizam as práticas mais eficientes para uso do idecomp em fluxos de trabalho de maior escala.

1. Prefira importações diretas de módulo quando o tempo de inicialização importa. Use from idecomp.decomp.nome_modulo import NomeClasse em vez de from idecomp.decomp import NomeClasse para evitar o carregamento antecipado de todos os 41 módulos. Aplique essa estratégia em scripts invocados repetidamente por pipelines de automação.

2. Use geradores para processar arquivos em lote sem acumular todos na memória. Se o objetivo é calcular estatísticas ou filtrar linhas, um gerador evita carregar todos os DataFrames simultaneamente:

from pathlib import Path
import pandas as pd
from idecomp.decomp.dec_oper_sist import DecOperSist

def gerar_tabelas(padrao):
    for caminho in Path("./resultados").glob(padrao):
        arq = DecOperSist.read(str(caminho))
        yield arq.tabela
        del arq

# Concatena sob demanda, sem manter todos os objetos vivos ao mesmo tempo
df_total = pd.concat(gerar_tabelas("*/dec_oper_sist.csv"), ignore_index=True)

3. Filtre os DataFrames imediatamente após a extração. Quanto antes as linhas irrelevantes forem descartadas, menor será o consumo de memória ao longo do processamento. Aplique filtros baseados em colunas como estagio, cenario ou patamar logo após acessar a propriedade do arquivo:

from idecomp.decomp.dec_oper_sist import DecOperSist

arq = DecOperSist.read("./dec_oper_sist.csv")
df = arq.tabela

# Filtra apenas o primeiro estagio antes de qualquer outra operacao
df_filtrado = df[df["estagio"] == 1].copy()
del arq, df

4. Libere objetos de arquivo com ``del`` após extrair os dados necessários. Objetos de arquivo do idecomp não são referências leves — eles armazenam a representação estruturada completa do arquivo. Descartá-los explicitamente com del após a extração do DataFrame é especialmente importante em loops com muitas iterações.

5. Utilize ``gc.collect()`` em lotes muito grandes. Para processamentos com centenas de arquivos de grande volume, chame gc.collect() periodicamente (por exemplo, a cada 50 arquivos) para garantir que objetos descartados sejam de fato liberados antes que o Python reutilize a memória. Avalie o impacto com tracemalloc antes e depois de adotar essa prática:

import gc
import tracemalloc
from pathlib import Path
import pandas as pd
from idecomp.decomp.dec_oper_sist import DecOperSist

tracemalloc.start()

dfs = []
caminhos = list(Path("./resultados").glob("*/dec_oper_sist.csv"))
for i, caminho in enumerate(caminhos):
    arq = DecOperSist.read(str(caminho))
    dfs.append(arq.tabela)
    del arq
    if (i + 1) % 50 == 0:
        gc.collect()

df_total = pd.concat(dfs, ignore_index=True)

memoria_atual, memoria_pico = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Pico de memoria: {memoria_pico / 1024 / 1024:.1f} MB")