Usando SIMD no h3all
SIMD_h3allAcelerando o Alinhamento de Sequências com SIMD
June 3rd, 2026

Às vezes a solução mais lenta deixa o programa mais rápido

Recentemente eu estava trabalhando em uma otimização para um alinhador exato de sequências biológicas chamado PA-Star.

O objetivo parecia simples: reduzir o tempo total de execução.

Mas o caminho para chegar lá era contraintuitivo.

Na literatura de Multiple Sequence Alignment (MSA) existe uma heurística chamada h₂_all que é usada há bastante tempo para guiar buscas A-Star. Ela funciona bem, é relativamente barata de calcular e aparece em praticamente todas as implementações paralelas modernas.

O problema é que ela não é muito inteligente.

Ela estima o custo restante do alinhamento olhando apenas para pares de sequências.

E quando você está tentando alinhar várias sequências ao mesmo tempo, olhar apenas para pares significa perder muita informação.

A consequência é que o algoritmo A-Star acaba explorando muito mais estados do que realmente precisa.

E isso custa caro.

A heurística melhor já existia

O curioso é que a literatura já conhecia uma alternativa melhor.

Ela se chama h₃_all.

Em vez de analisar pares de sequências, ela analisa trios.

Isso produz um limite inferior muito mais forte para o A-Star.

Na prática, significa que o algoritmo consegue eliminar muito mais caminhos inviáveis antes mesmo de explorá-los.

Menos nós expandidos.

Menos memória.

Menos trabalho.

Então surge uma pergunta natural:

Se h₃_all é melhor, por que ninguém usa?

Porque ela é absurdamente mais cara de calcular.

Enquanto a abordagem baseada em pares trabalha com programação dinâmica bidimensional, a abordagem baseada em trios exige programação dinâmica tridimensional.

O custo explode rapidamente.

Durante anos, isso fez com que h₃_all permanecesse mais como uma curiosidade teórica do que como uma solução prática.

A aposta

Nós decidimos testar uma hipótese simples.

E se o ganho obtido durante a busca compensasse o custo extra do pré-processamento?

Em outras palavras:

E se gastar mais tempo calculando a heurística fizesse o programa terminar mais rápido?

Para descobrir isso, substituímos a heurística original do PA-Star por uma implementação baseada em h₃_all.

Mas havia um problema.

Se fizéssemos isso de forma ingênua, o pré-processamento se tornaria inviável.

Então precisávamos atacar o custo computacional diretamente.

Paralelismo em dois níveis

A solução acabou combinando duas estratégias.

A primeira foi paralelizar os cálculos dos trios entre múltiplas threads.

Cada alinhamento de três sequências é independente dos demais.

Isso significa que podemos distribuir os trios entre diferentes núcleos sem praticamente nenhuma sincronização.

A segunda estratégia foi usar SIMD.

Reestruturamos a memória para armazenar as matrizes tridimensionais em vetores lineares contínuos.

Isso melhorou a localidade de cache e permitiu processar várias células simultaneamente usando instruções vetoriais.

A inspiração veio do trabalho clássico de Farrar sobre o algoritmo Striped Smith-Waterman.

Não copiamos a técnica diretamente, mas seguimos a mesma filosofia: reorganizar os acessos à memória para aproveitar melhor o hardware moderno.

O resultado mais interessante

O que eu esperava encontrar era uma troca.

Algo como:

"o pré-processamento ficou mais rápido e a busca ficou parecida".

Não foi isso que aconteceu.

Na verdade, o pré-processamento ficou mais caro.

Mas a busca ficou muito mais barata.

Em um dos datasets, o tempo gasto calculando a heurística aumentou de praticamente zero para 0,67 segundos.

Parece ruim.

Só que a fase principal da busca caiu de 189 segundos para 78 segundos.

O resultado final foi uma redução do tempo total de execução de 189,65 segundos para 80,01 segundos.

Um speedup de 2,37x.

Tudo isso sem alterar o algoritmo A-Star em si.

A única mudança foi fornecer uma estimativa melhor.

A melhor otimização nem sempre está no código quente

Esse trabalho reforçou uma coisa que eu vejo repetidamente em computação de alto desempenho.

Nem toda otimização vem de acelerar o trecho mais executado do programa.

Às vezes a melhor otimização é reduzir a quantidade de trabalho que precisa ser feito.

Foi exatamente isso que aconteceu aqui.

Gastamos mais processamento produzindo uma heurística melhor.

Em troca, o algoritmo passou a explorar muito menos estados.

O resultado foi menos tempo de execução e menos consumo de memória.

No maior caso avaliado, também observamos reduções superiores a 20% no uso de RAM.

O que eu aprendi

Quando comecei esse projeto, eu enxergava h₃_all como uma heurística teoricamente interessante, mas impraticável.

Depois dos experimentos, minha opinião mudou.

O custo adicional existe.

Mas em muitos cenários ele é amplamente compensado pela redução do espaço de busca.

E isso muda completamente a conversa.

Durante anos a pergunta foi:

"Como podemos conviver com o custo de h₃_all?"

Talvez a pergunta correta seja:

"Como conseguimos viver tanto tempo sem usar h₃_all?"