6 pontos por GN⁺ 2025-07-21 | 3 comentários | Compartilhar no WhatsApp
  • A equipe de desenvolvimento do FFmpeg anunciou um ganho de desempenho de até 100x por meio de código assembly escrito manualmente
  • Neste patch, o efeito de melhora de velocidade ocorre apenas em funções específicas, e não no programa inteiro
  • Com suporte a AVX512 em CPUs mais recentes, foi observado ganho de 100x; com suporte a AVX2, houve aumento de desempenho de 64%
  • Esse recurso de melhoria foi aplicado principalmente a um filtro pouco conhecido
  • A otimização automática do compilador ainda mostra uma diferença de desempenho em relação ao código assembly escrito manualmente

Melhoria de desempenho no FFmpeg: o que realmente significa um ganho de 100x

Patch mais recente e principais avanços

  • A equipe do projeto FFmpeg destacou o resultado de “ganho de velocidade de 100x” em sua ferramenta open source e multiplataforma de transcodificação de mídia, após aplicar código assembly escrito à mão
  • No entanto, os desenvolvedores também deixaram claro que essa afirmação se aplica a uma única função, e não ao FFmpeg inteiro
  • Essa otimização excepcional foi feita na função rangedetect8_avx512, alcançando até 100x de melhoria em processadores com suporte ao AVX512 mais recente, enquanto no caminho AVX2 o ganho foi de cerca de 64%
  • O recurso foi aplicado a um filtro pouco conhecido, que antes não estava entre as prioridades de desenvolvimento, mas agora recebeu otimização de processamento paralelo com SIMD (Single Instruction, Multiple Data)

Explicação dos desenvolvedores e contexto técnico

  • Em publicação no Twitter, os desenvolvedores do FFmpeg esclareceram: “esta única função ficou 100x mais rápida, não o FFmpeg inteiro”
  • Em explicação adicional, afirmaram que, dependendo do sistema, esse recurso pode permitir ganho de velocidade de até 100%
  • Essa melhora de desempenho é resultado do uso de tecnologia SIMD, que aumentou drasticamente a eficiência do processamento paralelo em chips modernos

A importância de escrever assembly manualmente

Abordagens de otimização no passado e no presente

  • Nas décadas de 1980 e 1990, em ambientes de hardware limitados, o código assembly escrito manualmente era uma ferramenta central para melhorar a velocidade de jogos e diversos softwares
  • Hoje, a maioria dos compiladores modernos converte código de linguagens de alto nível em assembly, mas, devido a limitações de otimização do compilador, como alocação de registradores, o assembly escrito manualmente ainda pode oferecer desempenho superior
  • O FFmpeg é um dos poucos projetos que continuam seguindo essa filosofia de otimização e até mantém seu próprio tutorial de assembly

A influência do FFmpeg no ecossistema

  • As bibliotecas e ferramentas do FFmpeg funcionam em diversos ambientes, como Linux, Mac OS X, Microsoft Windows, BSD e Solaris
  • Programas populares de reprodução de vídeo, como o VLC, também utilizam as bibliotecas libavcodec e libavformat do FFmpeg
  • Isso mostra a grande importância técnica do FFmpeg dentro do amplo ecossistema open source de codificação e decodificação de mídia

Encerramento

  • Embora essa melhora de desempenho se limite a algumas funções, e não ao conjunto completo dos recursos centrais, ela demonstra um caso claro de superação de limites de performance
  • A combinação de otimizações especializadas para hardware moderno com o espírito open source continua fazendo do FFmpeg uma referência técnica na área de processamento de mídia

3 comentários

 
bobcat 2025-07-24

Isso valia no passado e continua valendo hoje
De forma parecida, ao portar uma biblioteca de codec para ARM, comecei desfazendo um por um os kernels feitos em SSE, e quando terminei tudo e só restou a versão escalar, ao rodar benchmarks de mundo real a diferença de desempenho foi significativa.

 
aer0700 2025-07-23

Um engenheiro capaz de escrever código mais otimizado do que o GCC... impressionante.

 
GN⁺ 2025-07-21
Opiniões do Hacker News
  • Durante 10 anos fazendo otimizações SIMD para HEVC e afins, comparar a versão em assembly com a versão comum em C era quase uma piada, porque às vezes saíam números absurdos como 100x de diferença. Na prática, isso também significa que a eficiência inicial era extremamente baixa. Em uso real, o ambiente não é como um microbenchmark, com o cache cheio e a mesma função sendo chamada milhões de vezes em sequência. Em vez disso, numa situação real ela pode ser chamada só uma vez no meio de várias outras tarefas. Para reduzir o efeito do cache, seria preciso criar uma área de memória de teste bem grande, mas fico em dúvida se isso realmente é feito.

    • Também tenho curiosidade se softwares de conversão de vídeo como o FFmpeg fazem benchmarks “macro” separados. Parece que faria sentido medir desempenho, qualidade e uso de CPU ao longo de muito tempo, com vários vídeos e combinações de conversão. Mas, para isso, imagino que seria necessário hardware dedicado e consistente.

    • Fugindo um pouco do assunto, mas como você parece ter bastante experiência com SIMD, queria perguntar se já usou ISPC e o que acha dele. Ainda me parece meio absurdo que, mesmo hoje, continue sendo necessário escrever código SIMD manualmente e que compiladores comuns ainda sejam fracos em autovetorização, em contraste com kernels de GPU, onde isso já acontece automaticamente há muito tempo.

    • Acho que o próprio ffmpeg também não é tão diferente de um microbenchmark. Essencialmente, ele segue uma estrutura como while (read(buf)) write(transform(buf)).

    • Infelizmente, além da questão de cache, às vezes os desenvolvedores falavam em aumento de 100% na velocidade, mas na prática isso se aplicava só a filtros muito específicos. Ainda assim, em geral eles são bem transparentes ao transmitir essas informações.

    • Sobre a interpretação de que “era ineficiente no início”, acho que o importante é quais números saem; o resultado em si é o que importa.

  • Fiquei confuso porque em algumas partes da matéria fala em 100x, e em outras em ganho de 100% de velocidade. Por exemplo, diz que “o desempenho de rangedetect8_avx512 aumentou 100,73%”, mas a captura de tela mostra 100,73x. Se for 100x, isso seria um aumento de 9900%; se for 100% de velocidade, seria 2x mais rápido. Queria saber qual dos dois está correto.

    • Pelo que aparece na captura de tela, claramente o correto é 100x (ou 100,73x), o que significa um aumento de 9973% na velocidade. Parece que o símbolo de % foi usado errado no texto da matéria.

    • Para a função isolada, é 100x; para o filtro como um todo, 100% (2x).

    • A alegação do ffmpeg é 100x. Os 100% parecem ser um erro de digitação da matéria.

    • O nome da função está relacionado a “8” e, se ela opera sobre valores de 8 bits, então, se a implementação anterior era escalar, com AVX512 dá para processar 128 elementos de uma vez, então acho plausível haver ganho de 100x.

  • O FFmpeg tem orientações oficiais sobre escrita de assembly, então deixo como referência: https://news.ycombinator.com/item?id=43140614

  • Pela matéria, não ficou claro em que situação exatamente rangedetect8_avx512 é usado, nem qual seria o ganho real de desempenho em tempo real no processo completo de conversão. Fico me perguntando se isso de fato tem impacto prático perceptível.

    • Antigamente, quando o sinal de vídeo era analógico, sinais de controle eram codificados dentro da banda. Mesmo depois do DVD, como o sinal digital ainda acabava sendo emitido como analógico, valores de cor abaixo de 16 (dentro de 0–255) eram usados como sinal “mais preto que preto”, e o mesmo vale para BluRay e HDMI. Mais recentemente, a tendência passou a ser usar toda a faixa de 0–255. Mas ainda hoje é comum a distinção de faixa do sinal ser tratada incorretamente e a imagem acabar escurecida. Essa função parece servir para determinar se os valores de pixel estão em 16–255 ou em 0–255. Se ficar claro que é 16–255, dá para economizar informação na codificação. Mas isso é só suposição. Também trabalho com vídeo profissionalmente e tenho vergonha de ainda errar no tratamento do nível de preto.

    • Esse filtro não é usado no processo de conversão em si, e sim para detectar informações de metadados, como faixa de cor ou se o alfa é premultiplicado. A função em questão é especificamente sobre identificar a faixa de cor.

  • Queria mencionar uma experiência interessante: a única vez em que escrevi código em assembly também foi por causa de SIMD. Conversei sobre isso recentemente e tinha até esquecido por que precisei usar assembly na época. Na verdade, o compilador não estava conseguindo otimizar como eu queria por causa de problemas de aliasing. Depois que deixei claro que os dados não seriam acessados de outras formas e usei uma palavra-chave não padrão, o compilador passou a usar instruções SIMD automaticamente. No fim, removi o assembly que eu tinha escrito à mão.

  • É meio irônico que essa otimização se aplique só a x86/x86-64 (AVX2, AVX512). Durante muito tempo, como todo mundo usava x86, havia mais chance de otimizações SIMD serem amplamente aplicáveis. Mas as novas arquiteturas de extensão foram bem ruins ou não tinham compatibilidade suficiente. E agora que o SIMD do x86 finalmente ficou bom, o x86 já não é mais o padrão, então ficou mais difícil depender disso.

    • O AVX512 tem vários subconjuntos de extensões, então, a menos que você use só as instruções básicas, a faixa de suporte muda de CPU para CPU. Codificadores modernos melhoraram bastante em desempenho multithread, mas ainda há limites. Já sofri num projeto embarcado por causa de problemas de compatibilidade com encoders de vídeo do SoC, e acabei testando o ffmpeg e obtendo resultados melhores usando vários núcleos da CPU.
  • Na verdade, me surpreendeu que assembly fosse mais rápido do que C otimizado. Os compiladores hoje em dia são tão bons que eu imaginava que escrever assembly manualmente só daria um ganho bem pequeno, mas percebi claramente que eu estava enganado. Decidi que algum dia ainda preciso aprender assembly de verdade.

    • Olhando os patches relacionados, o baseline existente (ff_detect_range_c) é um código C escalar bem genérico, e o ganho de velocidade veio da versão AVX-512 da mesma operação (ff_detect_rangeb_avx512). Os desenvolvedores do FFmpeg preferem escrever assembly manual com macros independentes da largura vetorial, mas parece que daria para expressar quase a mesma coisa com intrinsics da Intel também (no fim, a diferença prática grande seria mais em alocação de registradores). A essência da diferença de desempenho está em quão bem a vetorização foi feita. Em compiladores modernos, vetorização de loops que não sejam triviais é quase impossível, e mesmo assim geralmente só é tentada com opções como gcc -O3. Por isso, em operações de granularidade pequena como essas de 8 bits, fazer a vetorização manualmente com AVX/AVX2/AVX-512 costuma render pelo menos dezenas de vezes mais desempenho. Em CPUs modernas, às vezes também dá para escrever código escalar extremamente otimizado mais rápido do que o que o compilador gera, mas isso é raro e exige muito cuidado (cadeias de dependência, pressão sobre execution ports etc.). Eu mesmo já tive um caso com ganho de 40%. Links relacionados: baseline C, versão AVX512

    • Quando você se aproxima mais da otimização de baixo nível, em menos de uma hora acaba encontrando algum caso em que o compilador C faz algo estranho do nada. Se o código realmente for chamado com frequência extrema, essas questões de otimização passam a importar muito na prática. Exemplo: https://stackoverflow.com/questions/71343461/how-does-gcc-not-clang-make-this-optimization-deciding-that-a-store-to-one-str

    • Só de descer para intrinsics SIMD já dá para extrair desempenho com muito mais facilidade do que deixando a cargo do compilador. Já escrevi um guia em várias partes sobre isso: https://scallywag.software/vim/blog/simd-perlin-noise-i

    • Quase todos os trechos críticos para desempenho em bibliotecas C/C++ usam assembly escrito à mão (até funções simples como strlen). Os compiladores produzem resultados razoáveis na maior parte do tempo, mas existem poucos softwares importantes o bastante para justificar esse nível de investimento.

    • O ganho real de desempenho não vem do assembly, e sim do AVX512. Esse kernel é tão simples que, se fosse escrito com intrinsics AVX512, a diferença entre C e assembly seria mínima. O motivo do ganho de 100x é: a) min/max em SIMD viram instruções únicas, enquanto no escalar se desdobram em cmp + cmov; b) como a precisão é de 8 bits, cada instrução AVX512 processa 64 valores de uma vez. Como resultado, dá até para saturar a largura de banda dos caches L1 e L2 (128B e 64B/cycle no Zen 5). Porém, se for processado um quadro inteiro, o tamanho dos dados pode obrigar acesso ao L3, e nesse caso esse ganho cai pela metade; se nem couber no L3, o ganho é menor ainda.

  • Isso me lembrou o Sound Open Firmware (SOF), que também pode ser compilado escolhendo entre gcc e o compilador comercial Cadence XCC (com suporte a intrinsics SIMD do Xtensa HiFi): https://thesofproject.github.io/latest/introduction/index.html#toolchain-faq

  • É 100x, não 100% x.