2 pontos por GN⁺ 2025-02-23 | 1 comentários | Compartilhar no WhatsApp
  • A primeira aula de assembly do FFmpeg organiza, em nível introdutório, por que SIMD em assembly escrito à mão é necessário no processamento de multimídia e quais são as convenções do FFmpeg para escrever funções
  • O público-alvo deve conhecer ponteiros em C e os conceitos de escalar·vetor, adição e multiplicação em nível de ensino médio; a aula usa como base x86 64-bit e sintaxe Intel
  • No FFmpeg, assembly function, SIMD e vectorise são usados quase com o mesmo sentido, indicando o processamento simultâneo de vários elementos de dados, algo que combina bem com imagem, vídeo e áudio
  • Em termos de desempenho, há preferência por assembly manual; intrinsics costumam ser 10~15% mais lentos, e em comparações do dav1d a vetorização automática chegou a cerca de 2x, enquanto a versão escrita manualmente alcançou até 8x
  • A primeira função de exemplo usa x86inc.asm, INIT_XMM sse2, cglobal, movu, paddb e RET para somar via SIMD 16 bytes de dois buffers uint8_t e salvar o resultado de volta no primeiro buffer

Objetivo da aula e conhecimentos prévios

  • FFmpeg Assembly Language Lesson One é uma aula introdutória que aborda os fundamentos de como a linguagem assembly é escrita no FFmpeg e ajuda a entender o que realmente acontece dentro do computador
  • Os conhecimentos necessários são os seguintes
    • Linguagem C, especialmente ponteiros
    • Se você não conhece C, recomenda-se estudar The C Programming Language
    • Conceitos de escalares e vetores, adição e multiplicação em nível de matemática do ensino médio

Linguagem assembly e SIMD

  • Linguagem assembly é uma linguagem de programação em que se escreve, de forma legível para humanos, código que corresponde diretamente às instruções processadas pela CPU
  • O código assembly legível por humanos é convertido por um assembler em dados binários de código de máquina (machine code) que a CPU entende
  • O código assembly do FFmpeg é, em sua maior parte, do tipo SIMD (Single Instruction Multiple Data)
    • Uma única instrução opera simultaneamente sobre vários elementos de dados
    • Também é chamado de programação vetorial
    • A programação escalar comum processa um elemento de dados por vez
  • SIMD combina bem com processamento de imagem, vídeo e áudio, que lida com muitos dados organizados sequencialmente na memória
  • No FFmpeg, as expressões abaixo são usadas quase com o mesmo significado
    • assembly function
    • SIMD
    • vectorise
    • Ou seja, escrever manualmente uma função em assembly para processar vários elementos de dados ao mesmo tempo

Por que o FFmpeg escreve assembly diretamente

  • O objetivo principal é aumentar a velocidade do processamento multimídia
    • Ganhos de mais de 10x com código em assembly são comuns
    • Isso é importante para reduzir travamentos na reprodução de vídeo em tempo real
    • Também pode reduzir o consumo de energia e aumentar a duração da bateria
  • Funções de codificação e decodificação de vídeo são usadas em enorme volume por usuários finais e datacenters, então até pequenas melhorias se acumulam rapidamente
  • O FFmpeg usa assembly escrito à mão em vez de intrinsics
    • Intrinsics são funções parecidas com C que correspondem a instruções de assembly
    • Normalmente são 10~15% mais lentos que assembly escrito manualmente
    • Esse número varia conforme o compilador, e defensores de intrinsics podem discordar
    • Também há a opinião de que, por usar Hungarian Notation, o código fica difícil de ler
  • Inline assembly ainda pode aparecer em alguns trechos antigos do FFmpeg ou em projetos como o Linux Kernel
    • É a abordagem de escrever assembly diretamente dentro do código C, em vez de usar um arquivo separado
    • Em projetos como o FFmpeg, prevalece a opinião de que isso dificulta a leitura, não tem suporte amplo dos compiladores e é difícil de manter
  • A recomendação é ignorar, para fins de aprendizado, a ideia de que a vetorização automática do compilador já seria suficiente
    • Em testes recentes do projeto dav1d, a vetorização automática mostrou ganho de cerca de 2x
    • Versões escritas manualmente puderam chegar a 8x

Escopo da sintaxe e materiais de referência

  • A aula foca em assembly x86 64-bit
    • Também chamado de amd64, e funciona em CPUs Intel
    • Há possibilidade de expansão futura para assembly de outras CPUs, como ARM e RISC-V
  • A sintaxe de assembly x86 tem variantes AT&T e Intel
    • A sintaxe AT&T é mais antiga e considerada mais difícil de ler do que a sintaxe Intel
    • A aula usa sintaxe Intel
  • Livros gerais ou materiais online como Stack Overflow podem não ser especialmente úteis como referência para assembly do FFmpeg
    • Isso porque ele usa assembly em sintaxe Intel escrito à mão
    • Muitos materiais online focam em programação de sistema operacional, programação de hardware ou código sem SIMD
    • O assembly do FFmpeg segue uma abordagem própria, especializada em processamento de imagem de alto desempenho
  • Os diagramas que visualizam instruções SIMD e seu funcionamento na parte final de The Art of 64-bit assembly podem ser úteis
  • É disponibilizado um servidor no Discord para perguntas

Conceito básico de registradores

  • Registradores são áreas dentro da CPU onde os dados são processados
  • A CPU não opera diretamente sobre a memória; ela primeiro carrega os dados em registradores, processa e depois grava de volta na memória
  • Em assembly, normalmente não é possível copiar dados diretamente de uma posição de memória para outra; é preciso passar primeiro por um registrador

Registradores de uso geral

  • GPR (General Purpose Register) é um registrador de uso geral que pode armazenar dados ou endereços de memória
    • Aqui ele pode armazenar valores de até 64-bit
    • Também pode armazenar ponteiros
    • Permite operações como adição, multiplicação e shift
  • Muitos livros de assembly tratam longamente dos detalhes dos GPRs e de seu contexto histórico
  • No código assembly do FFmpeg, os GPRs são usados principalmente como andaime (scaffolding), e grande parte dessa complexidade não é necessária ou fica abstraída

Registradores vetoriais e tamanho dos dados

  • Registradores vetoriais armazenam vários elementos de dados
  • Os principais registradores vetoriais do x86 são os seguintes
    • mm: registradores MMX, com tamanho de 64-bit, históricos e hoje pouco usados
    • xmm: registradores XMM, com tamanho de 128-bit, amplamente disponíveis
    • ymm: registradores YMM, com tamanho de 256-bit, cujo uso traz alguma complexidade
    • zmm: registradores ZMM, com tamanho de 512-bit, com uso mais limitado
  • A maior parte dos cálculos em compressão e descompressão de vídeo é baseada em inteiros, então a aula também se concentra em inteiros
  • Um registrador xmm de 128-bit pode ser interpretado da seguinte forma
    • 16 bytes, cada um com 8-bit
    • 8 words, cada um com 16-bit
    • 4 doublewords, cada um com 32-bit
    • 2 quadwords, cada um com 64-bit
  • As abreviações passam a ser importantes depois
    • byte: dado de 8-bit
    • word: dado de 16-bit
    • doubleword: dado de 32-bit
    • quadword: dado de 64-bit
    • double quadword: dado de 128-bit

O papel de x86inc.asm

  • x86inc.asm é uma camada leve de abstração usada por FFmpeg, x264 e dav1d
  • Ele fornece vários recursos para ajudar programadores de assembly a escrever código com mais facilidade
  • Um dos recursos importantes no começo é atribuir rótulos como r0, r1, r2 aos GPRs
    • Assim, não é preciso decorar os nomes reais dos registradores
    • Como os GPRs no FFmpeg servem principalmente como andaime, isso reduz o esforço de escrita

Exemplo simples de asm escalar

mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
  • A primeira linha armazena o valor imediato (immediate value) 3 no registrador r0 como quadword
    • Valor imediato é um valor armazenado no próprio código assembly, e não carregado da memória
  • Na sintaxe Intel, o operando-fonte à direita é transferido para o operando-destino à esquerda
    • Dá para ler como r0q = 3
    • A ordem é parecida com o comportamento de memcpy
  • O sufixo q em r0q indica que o registrador está sendo usado como quadword
  • Depois disso, o comportamento é o seguinte
    • Com inc, o valor vira 4
    • Com dec, o valor volta para 3
    • Com imul, ele é multiplicado por 5, então r0q termina com 15
  • Instruções legíveis por humanos como mov e inc são chamadas de mnemonics
    • O assembler as converte em código de máquina
    • MOV, INC em maiúsculas e mov, inc em minúsculas são equivalentes
    • No FFmpeg, os mnemonics são escritos em minúsculas e as maiúsculas ficam reservadas para macros

Primeiro exemplo de função SIMD

%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t * src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
  • Essa função soma via SIMD os dados de src e src2 e grava novamente o resultado no endereço de src
  • %include "x86inc.asm" inclui helpers, nomes predefinidos e macros desenvolvidos pelas comunidades de x264, FFmpeg e dav1d
  • SECTION .text indica a seção onde fica o código executável
    • Dados constantes podem ficar na seção .data
  • ;static void add_values(uint8_t *src, const uint8_t * src2) é um comentário que mostra o formato dos argumentos da função em C
    • Em assembly, o ponto e vírgula ; funciona como comentário, como // em C
  • INIT_XMM sse2 define o uso de registradores XMM e do conjunto de instruções sse2
    • Isso porque paddb é uma instrução sse2
  • cglobal add_values, 2, 2, 2, src, src2 define a função C add_values
    • A função recebe 2 argumentos
    • O número de GPRs usados pela função, incluindo os argumentos, é 2
    • O número de registradores XMM usados é 2
    • Os dois itens finais são os rótulos dos argumentos da função, src e src2
    • Em código mais antigo, pode-se usar diretamente GPRs como r0 e r1 sem rótulos de argumento

load, packed add, store

movu m0, [srcq]
movu m1, [src2q]
  • movu é uma forma abreviada de movdqu, que significa move double quad unaligned
  • O alinhamento (alignment) será tratado em aulas posteriores; aqui basta entender como uma instrução que move 128-bit a partir de [srcq]
  • Em mov, os colchetes indicam que o endereço deve ser dereferenciado
    • É um conceito parecido com *src em C
    • Essa operação é um load
  • O sufixo q indica o tamanho do ponteiro
    • Em sistemas 64-bit, representa 8 bytes, que é o tamanho do ponteiro em C
    • Em sistemas 32-bit, x86asm usa 32-bit
    • O load real continua sendo de 128-bit
  • Registradores vetoriais são referenciados com o nome abstraído m0, em vez do nome completo como xmm0
    • Isso se conecta a aulas futuras, em que o mesmo código pode se adaptar a vários tamanhos de registradores SIMD
paddb m0, m1
  • paddb soma entre si os elementos byte de cada registrador
  • O prefixo p significa packed e é usado para distinguir instruções vetoriais de instruções escalares
  • O sufixo b indica soma em unidades de byte
  • Ao somar dois registradores com 16 bytes cada, os elementos correspondentes são somados posição por posição, como a+q, b+r, c+s
movu [srcq], m0
RET
  • movu [srcq], m0 grava o dado resultante de volta no endereço apontado pelo ponteiro srcq
    • Essa operação é um store
  • RET é uma macro que indica o retorno da função
  • Quase todas as funções em assembly do FFmpeg modificam os dados recebidos por argumento em vez de retornar valores
  • Nos exercícios, a continuação envolve criar function pointers para as funções assembly disponíveis e usá-los

1 comentários

 
GN⁺ 2025-02-23
Opiniões no Hacker News
  • Outro material sobre o mesmo tema: https://blogs.gnome.org/rbultje/2017/07/14/writing-x86-simd-...

    Olhando os comentários aqui, a utilidade de SIMD escrito à mão varia de “totalmente incerta” a “essencial para a missão”, mas parece haver mais gente no lado do “totalmente incerta”, então vou explicar um pouco o lado essencial para a missão
    O FFmpeg é um caso evidente por causa da frequência de uso, mas acho que dav1d, um decodificador AV1 de produção e uso geral, é um exemplo mais fácil para quantificar o impacto de SIMD escrito à mão
    O dav1d é usado praticamente everywhere, dos principais navegadores ao sistema operacional Android, substituiu o libgav1, e uma grande razão de seu sucesso é sua velocidade enorme, possível porque uma parte considerável da base de código é SIMD escrito à mão
    É bom que linguagens como Zig tenham suporte embutido a SIMD, mas, em alguns usos em que também vale investigar a possível diferença de desempenho, acaba sendo necessário escrever diretamente. Certas linhas de código do dav1d rodam trilhões de vezes por dia, então precisam ser tão rápidas quanto possível, e a diferença entre SIMD escrito à mão e SIMD gerado pelo compilador pode chegar a 50% em alguns casos, o que importa
    Eu também estou envolvido, até certo ponto, em escrever código que será executado uma quantidade enorme de vezes, então acho que materiais como a escola de linguagem assembly do FFmpeg são bem importantes para que esse tipo de técnica não desapareça

    • Uma das coisas interessantes do dav1d é que ele é escrito em assembly, então pode usar sua própria convenção de chamada
      A convenção de chamada pode até variar de método para método, o que resulta em muito menos gravações e leituras na pilha do que o código gerado por um compilador que segue a convenção de chamada normal da plataforma
    • Eu também estou no lado do essencial para a missão, mas há contraexemplos interessantes. Quando você se concentra em pequenos detalhes ou cai em complexidade acidental, pode deixar de enxergar otimizações algorítmicas; e, se houver código específico demais por plataforma, aumentando muito o atrito para mudanças, fica difícil sair de um mínimo local
      Por exemplo, nossa nova multiplicação de matrizes é mais rápida que bibliotecas famosas para inferência de LLM e, em alguns casos, isso acontece mesmo quando o outro lado usa AMX e nós usamos AVX512BF16. O motivo talvez seja um gargalo de threading, ou talvez outra causa, mas quando há JIT envolvido fica difícil entender
      Se tivéssemos precisado escrever kernels específicos por plataforma manualmente, esse resultado não teria acontecido. O tempo no dia é limitado e, graças a escrever uma única implementação com Highway, conseguimos explorar um espaço de projeto mais amplo, incluindo um autotuner que escolhe não só novos tipos de kernel e tamanhos de bloco, mas também estratégias de paralelização e seus parâmetros
      Em uma segunda etapa, até dá para ajustar algumas partes à mão, mas espero que a exploração mais ampla venha antes de micro-otimizar alocação de registradores e convenções de chamada
    • Fico curioso para saber o que o Zig oferece como suporte embutido a SIMD além de sobrecarga para operações aritméticas triviais. 90% da utilidade de SIMD está fora dessas operações simples
      Gosto de Zig, mas entendo que, na maioria dos casos, ainda é preciso usar intrinsics específicos de CPU, como em C/C++
      GCC e Clang dão suporte ao atributo vector_size e à sobrecarga de operadores aritméticos desses tipos “vetorizados”, além de muito mais. Na prática, intrinsics como _mm256_mul_ps também são implementados como #define _mm256_mul_ps(a,b) (__m256)((v8sf)(a) * (v8sf)(b))
      A utilidade desses recursos é muito maior do que o que é possível em Zig
    • Exatamente. No lado oposto, o dos encoders, SIMD escrito à mão também é muito usado por motivos semelhantes
      No caso de encoders, muitas vezes é preciso “estruturar” o problema de modo a permitir eliminar loops e loads cedo, e compiladores não conseguem gerar esse tipo de código vetorizado automaticamente
  • Antigamente eu criava muitas versões SIMD de funções centrais, mas hoje quase não faço isso. Uma abordagem que vale tentar é isolar esse código e rodá-lo no excelente Compiler Explorer [0]
    Depois é só examinar o código gerado
    Hoje em dia, a vetorização automática muitas vezes gera versões SIMD muito boas das funções, e frequentemente basta dar “dicas” ao compilador. Por exemplo, explicitar o alinhamento ou fornecer diretamente os tipos de origem/destino dos vetores
    Dá para fazer muita coisa ao “estilizar” o código C pensando no que o compilador pode fazer. Usar mais variáveis intermediárias e dividir a operação desejada em partes bem pequenas, por exemplo
    Mesmo no pior caso, se o compilador não for inteligente o suficiente, dá para modificar com base no assembly gerado sem escrever manualmente todo o boilerplate
    Na maioria dos casos, a função C resultante é vetorizada tão bem quanto, ou melhor que, a que eu teria escrito à mão; e, em muitos outros casos, fica “perto o suficiente” para não fazer grande diferença. Além disso, esse código tem grande chance de também ser bem vetorizado para WASM, NEON etc., sem versões explícitas
    [0] https://godbolt.org/

    • Nós fizemos algo meio parecido. Em casos muito raros e isolados, por exemplo uploads/downloads de imagens e conversões que não eram suportados pelo driver da GPU ou não eram grandes o suficiente para iniciar um trabalho na GPU, começávamos escrevendo em C e usávamos anotações de compilador, como alinhamento ou aliasing de ponteiros permitido, para gerar o código desejado
      Tanto GCC quanto Clang dão suporte a algumas extensões vetoriais, então é possível implementar de forma razoavelmente portável coisas como loads/stores dispersos, shuffles e mascaramento de elementos em um único registrador — coisas que são difíceis de expressar em “C puro” de modo legível para humanos e que sempre produzam claramente o código esperado entre versões do compilador
      Mas, como também precisávamos dar suporte a outros compiladores e plataformas, nos builds reais acabávamos usando o assembly gerado a partir desse arquivo-fonte
    • Como contraexemplo, encontro regularmente casos triviais que os compiladores não conseguem vetorizar automaticamente bem: https://gcc.godbolt.org/z/rjEqzf1hh
      Isso é adição saturada de bytes sem sinal. Tanto em x86-64 quanto em ARM64 há suporte direto com uma única instrução, PADDUSB e UQADD.16B
      Mas todos os compiladores se atrapalham com a descrição intuitiva, falhando em vetorizar ou gerando código vetorizado maior e mais lento do que o necessário
      E isso apesar de ser uma primitiva de vetorização básica e simples. Fazer o compilador usar instruções mais complexas, como deslocamento à direita saturado com estreitamento e arredondamento (UQRSHRN), é difícil ou quase impossível
    • O problema é que você precisa examinar a saída do compilador e compará-la com o esperado. Talvez seja preciso mexer um pouco até ficar equivalente ao código que você escreveria à mão
      Normalmente é mais rápido simplesmente escrever direto
    • Pela minha experiência, vetorização automática é uma otimização frágil que falha silenciosamente em todo tipo de condição, então eu não gostaria de depender dela
    • Não sei como fazer o compilador gerar pshufb mais largo que 16 bytes no caso geral
      Mesmo no caso de largura de 16 bytes, se você usar a definição real de pshufb, não obtém pshufb; se usar uma versão com comportamento indefinido, aí sai pshufb
  • Sou o autor desta aula
    Podem perguntar qualquer coisa

    • Como usuário de Mac ARM, tenho curiosidade: quanto esforço dá fazer esse tipo de código otimizado se comportar da mesma forma em todas as plataformas? Imagino que haja testes muito rigorosos e algoritmos alternativos
      Com tanto assembly, parece um milagre que o FFmpeg funcione no meu Mac. Foi portado manualmente?
    • Tenho uma pergunta como alguém que mal consegue ler assembly, mas ainda não tem a intuição de decompor ideias e escrevê-las em assembly. Você recomenda algum jeito de aprender ou melhorar isso?
      Tenho curiosidade sobre em que momento você percebe “isso poderia ficar mais rápido em assembly”. Se encontrou uma função que realmente ficaria rápida em assembly, como a escreve? Pega a saída que o compilador transformou em assembly e começa daí, ou escreve do zero? Isso sequer importa?
    • Tenho curiosidade sobre como você vê conjuntos de instruções SIMD de largura variável, como ARM SVE ou a extensão RISC-V V
      Como a usabilidade para desenvolvedores e o desempenho do código se comparam ao SIMD tradicional? Estamos indo para um mundo com menos tipos de conjuntos de instruções SIMD para programar?
    • Como o FFmpeg gera tabelas SEH para funções assembly no Windows? Fico curioso se x86asm.inc cuida disso ou se simplesmente não se preocupam com isso
    • Como alguém que escreveu código otimizado para x86 profissionalmente nos anos 90, ainda é preciso fazer isso manualmente em 2025?
      Não daria para simplesmente escrever os testes e deixar um LLM tentar 10.000 algoritmos e depois perfilar os resultados?
      Ou, mesmo dando 10.000 seeds aleatórios, ainda seria difícil para um LLM encontrar o ótimo?
      Pergunto só por curiosidade. Otimizar x86 à mão não é fácil. Para pensar nisso, é preciso encaixar todos os registradores na cabeça e avaliar as combinações, além de saber quanto tempo cada combinação de instruções vai levar. E algumas instruções têm casos especiais estranhos, difíceis para uma pessoa considerar, em que podem levar muito mais tempo ou ficar muito mais rápidas
  • Tenho curiosidade sobre quem já fez isso na prática. Ao aprender ou implementar assembly, há algum prazer como em LISP ou RISC-V, ou é algo que se aprende para fazer outra coisa, como aprender COBOL por causa de algum trabalho específico em sistemas?
    Sempre tive interesse, mas no trabalho do dia a dia não tenho exatamente um motivo para me aprofundar. Fico pensando se vale investir tempo por diversão

    • Fiz os primeiros 27 capítulos deste tutorial porque queria aprender mais, e foi muito divertido: https://mariokartwii.com/armv8/
      Agora eu até gosto bastante de programar em assembly. Depois do tutorial, não fiz muita coisa além de criar uma biblioteca de arrays que pode ser chamada a partir de C
      Acho divertido porque, nesse nível, sobra muito pouca mágica. Você diz exatamente o que precisa acontecer, e o que se vê geralmente é o que sai como resultado
      Também passei a entender linking muito melhor, e isso ajuda a compreender coisas que eu conhecia em alto nível, mas cujos detalhes eram nebulosos
      Agora também quero conferir este tutorial de FFmpeg. Este é x86, não ARM
    • Aprender ao menos uma linguagem assembly é muito recompensador. Ela coloca você em contato com a forma mais primitiva da programação real
      Existem modelos teóricos mais simples, como máquinas de Turing ou cálculo lambda, mas as arquiteturas com que programadores lidam de fato têm um certo grau de generosidade
      Não é algo para temer. Assembly é mais verboso do que complexo. Para cada coisa que você faz, há cargas e armazenamentos, cargas e armazenamentos, repetidos inúmeras vezes
      Se você acrescentar alguns macros e verificações em tempo de build, ou colocar isso no contexto de um sistema Forth com um interpretador que “executa blocos de assembly”, permitindo desenvolvimento interativo e scripting, ele não fica tão distante de C e remove a mágica do compilador
      Também recomendo uma abordagem retrô. Uma máquina de 8 bits em um emulador mantém o modelo de trabalho dentro de um espaço pequeno e bem documentado, e as restrições tornam valioso pensar em fazer mais coisas em assembly. Em arquiteturas pós-32 bits, quando há recursos em abundância, isso não é tão comum
      Quem desenvolve em assembly profissionalmente provavelmente terá preferências mais específicas, mas, para iniciantes, o mais importante é um ambiente com boa documentação e bons exemplos. O Rosetta Code tem bons exemplos de assembly que servem para estudo
    • Um ponto interessante é que ele é mais alto nível do que se imagina. O chip real faz coisas como predição de desvios e pipelining, e uma pessoa só consegue controlar uma parte muito pequena disso
      Lembro de uma disciplina na faculdade em que competíamos para ver quem criava o programa em assembly mais performático para uma determinada tarefa. Todo mundo tentava várias variações de loop unrolling para extrair o máximo desempenho e evitar predições de desvio ruins
      Não sei se atingi o Ballmer Peak na noite anterior à entrega, mas tentei uma configuração que quase todos os outros deixaram passar e venci por uma margem mínima
      Também há o enorme prazer de olhar para https://github.com/chrislgarry/Apollo-11 e poder brincar: “isso é um sistema Unix, eu conheço isso!”. A maravilha de conseguir ler a linguagem que levou a humanidade à Lua não desaparece
      Resposta curta: sim
    • Aprender assembly teve um significado profundo para mim. Em 30 anos programando, nunca usei de fato, mas completou o quadro dos transistores à programação de alto nível
      O momento em que você entende como transistores, portas lógicas, arquitetura de CPU e programação de alto nível se encaixam vale o esforço, mesmo que você nunca use assembly profissionalmente
    • Nos últimos cerca de 25 anos, mergulhei fundo em assembly por diversão
      Às vezes é útil, mas há um grande prazer em colocar até o último byte no lugar certo, explorar binários que ninguém examinou por décadas ou criar emuladores que antes seriam impossíveis
      É uma das poucas áreas em que ainda sinto a mágica como quando comecei
  • Pessoalmente, não vejo grande valor em escrever assembly diretamente em vez de usar intrinsics, mas ler assembly foi realmente útil
    Uso bastante o Compiler Explorer(https://godbolt.org/) para ver o assembly gerado e entender as otimizações que o compilador faz ao otimizar desempenho

    • Isso contradiz diretamente o conteúdo do texto
      “É para acelerar o processamento multimídia. Ao escrever código assembly, é muito comum obter melhorias de velocidade de mais de 10 vezes, o que é especialmente importante ao tentar reproduzir vídeo em tempo real sem engasgos”
  • Gostei de ver a referência ao K&R. Foi o livro que comprei para aprender C e programação em geral
    No começo tentei C++ como primeira linguagem, mas eu ficava querendo saber o que acontecia por dentro, então ela parecia abstrata demais e difícil de aprender

  • Este material é perfeito. Eu conhecia assembly x86 da época do 386, mas em processadores mais avançados ficou complexo demais
    Eu queria aprender mais sobre SIMD em CPUs modernas, e isto parece um excelente material

  • A frase “observe que o sufixo q se refere ao tamanho de ponteiro *(*ou seja, em C, em um sistema de 64 bits, significa *sizeof(*src) == 8, e x86asm é esperto o suficiente para usar 32 bits em um sistema de 32 bits), mas o carregamento real é de 128 bits” é confusa
    Acho que i.e deveria ser i.e.,, e o que significa *(*? Não deveria ser apenas um parêntese de abertura?
    Também não vejo em que contexto *sizeof(*src) poderia ser válido. Até onde sei, sizeof não produz um ponteiro
    Parece que a frase recebeu asteriscos aleatórios, ou que tentaram misturar asteriscos de itálico com sintaxe de C e deu errado

    • Sim. Parece que algo deu errado no próprio Markdown ou no processo de converter o material original para Markdown
    • Os dois primeiros asteriscos parecem ter sido usados como um par de notas de rodapé
    • Não retorna o tamanho do ponteiro? Parece que foi usado apenas para lidar com diferenças de arquitetura
  • Não me importo com a forma como foi dividido; só queria dizer que este guia é muito bom
    Eu gostaria que existisse algo assim quando eu me interessava por coisas de nível muito baixo

  • Assembly é 10 vezes mais rápida que C? Em algum momento isso certamente deve ter sido verdade, mas ainda é assim hoje? Os compiladores realmente ficaram tão estagnados a ponto de nem chegarem perto de assembly escrito à mão?

    • C usando intrínsecos pode chegar muito perto do desempenho de assembly puro
      Os desenvolvedores do FFmpeg são um tanto notoriamente contrários a intrínsecos e, se me lembro bem, não os aceitam na base de código mesmo que tenham desempenho tão bom quanto o assembly equivalente. Ainda assim, pela própria estimativa do texto, a diferença entre intrínsecos e assembly fica em torno de 10% a 15%
      Se você comparar assembly cuidadosamente otimizado com C ingênuo, é possível ver uma diferença de 10 vezes nos casos em que a vetorização é possível, mas o compilador não consegue aproveitá-la. Isso é comum: a autovetorização ainda costuma ser bem ruim além dos casos triviais
      Dito isso, não é surpresa que código escrito por especialistas supere muito código ingênuo
    • Não é uma questão de estagnação dos compiladores. O compilador não conhece as informações que o autor do assembly usa no projeto
      Dito de forma mais simples, um compilador C não consegue inferir, olhando apenas para uma implementação comum em C, que a intenção é expressar determinada matemática com intrínsecos SIMD mais eficientes. Isso porque ele não tem acesso à intenção matemática do autor
      Também há considerações específicas de cada alvo. Um compilador é, inevitavelmente, um compilador de propósito geral. Problemas de recursos, como alocação de registradores, são NP-completos e equivalentes ao problema da mochila. Quase ninguém quer que o compilador passe horas tentando encontrar a geração de assembly absolutamente ótima. Se é possível saber estaticamente essa otimalidade é outra questão
    • Isso trata de código altamente vetorizado que usa todos os hacks possíveis para aproveitar totalmente a CPU
      Compiladores são inteligentes em código comum, mas codecs não são código comum. Não sou programador do FFmpeg, mas tenho experiência trabalhando com áudio
    • Compiladores C ainda são bem ruins em autovetorização
      Em problemas aos quais SIMD se aplica, é razoável esperar algo de 2 a 16 vezes mais rápido do que uma implementação escalar ingênua
    • Provavelmente há coisas muito de nicho aí
      Eu não conseguiria escrever assembly 10 vezes melhor que C, mas não presumiria que ninguém consiga