- A recall é um serviço que oferece bots de reunião para centenas de empresas e opera uma infraestrutura em larga escala na AWS
- Para oferecer um serviço com boa eficiência de custos, a empresa tenta aproveitar ao máximo o desempenho do hardware
- Como a disponibilidade de GPUs em provedores de nuvem foi instável nos últimos anos, o processamento de vídeo foi feito na CPU em vez de GPU
- Ao perfilar bots que usam Chromium headless, descobriram que a maior parte do tempo de CPU não era gasta com processamento de vídeo (codificação/decodificação), mas sim nas funções de cópia de memória
__memmove_avx_unaligned_erms e __memcpy_avx_unaligned_erms
memmove e memcpy são funções da biblioteca padrão C (glibc) para copiar blocos de memória
memmove lida com alguns casos especiais relacionados à cópia de memória em regiões sobrepostas, mas ambas podem ser classificadas como funções de "cópia de memória"
- O sufixo
avx_unaligned_erms indica que elas são otimizadas para sistemas com suporte a Advanced Vector Extensions (AVX) e também para acessos a memória não alinhados
erms significa Enhanced REP MOVSB/STOSB, uma otimização para movimentação rápida de memória em processadores Intel modernos. Dá para entender como "uma implementação mais rápida para determinados processadores"
- O profiling mostrou que quem mais chamava essas funções era o cliente WebSocket em Python que recebia os dados
- Em seguida vinha a implementação de WebSocket do Chromium, responsável por transmitir os dados
Problemas do WebSocket
- Era usado um servidor WebSocket local para enviar dados brutos de vídeo do ambiente JS do Chromium para o encoder
- Um stream de vídeo bruto em 1080p 30fps exige uma largura de banda alta, de mais de 93 MB por segundo
- O uso de WebSocket gerava muito custo computacional, principalmente por causa de fragmentação e masking
- Fragmentação: a implementação de WebSocket do Chromium fragmenta mensagens acima de 131 KB em vários frames. Um frame bruto de vídeo com mais de 3 MB era dividido e enviado em mais de 24 frames separados
- Masking: por motivos de segurança, o WebSocket aplica masking a todos os frames enviados do cliente para o servidor. Em volumes acima de 100 MB por segundo, isso vira um overhead relevante
Buscando alternativas
- Como era difícil implementar algo muito mais performático que WebSocket usando apenas APIs de navegador, decidiram fazer um fork do Chromium e implementar funcionalidade personalizada
- Foram consideradas 3 alternativas: raw TCP/IP, Unix Domain Socket e Shared Memory
- TCP/IP: evita os problemas de fragmentação/masking do WebSocket, mas o tamanho máximo dos pacotes é pequeno, então ainda há problema de fragmentação. Também existe overhead de cópia para o espaço do kernel
- Unix Domain Socket: permite pular completamente a pilha de rede, mas ainda exige cópia de dados entre espaço de usuário e espaço do kernel
- Shared Memory: memória acessível simultaneamente por vários processos. O Chromium pode escrever diretamente na memória compartilhada sem cópias intermediárias, e o encoder pode ler dali imediatamente
Implementação da transmissão baseada em memória compartilhada
- A leitura e a escrita contínuas de dados na memória compartilhada foram implementadas no formato de ring buffer
- Requisitos: lock-free, múltiplos produtores/um único consumidor, tamanho de frame variável, leitura sem cópia, compatibilidade com sandbox e sinalização de baixa latência
- Avaliaram implementações prontas de ring buffer, mas como nenhuma atendia a todos os requisitos, decidiram implementar a própria solução
- Para permitir leitura sem cópia, os ponteiros foram divididos em três: write, peek e read
- Para garantir thread safety, foram usadas operações atômicas, e para sinalizar chegada de novos dados/liberação de espaço, foi usado um named semaphore
- Com a implementação do ring buffer em memória compartilhada e outras otimizações, foi possível reduzir o uso de CPU dos bots em até 50%. No fim, isso gerou uma economia anual de mais de US$ 1 milhão na AWS.
3 comentários
Comentários do Hacker News
É uma história típica de uma startup que escolheu um atalho "bom o suficiente" e otimizou depois.
Há quem diga que a alta largura de banda dos dados brutos de vídeo é surpreendente.
Há a opinião de que o problema não era a AWS, mas o desperdício de ciclos de CPU.
Apontam que o MTU e o MSS de redes TCP/IP são pequenos em comparação com o tamanho dos frames de vídeo.
Há quem diga que, usando o Mojo do Chromium, não seria necessário se preocupar com código específico de cada plataforma.
Há a opinião de que o problema não era a rede, mas a falta de compreensão sobre codecs de vídeo.
Elogiam a transparência e dizem que também gostariam de transparência nos preços do produto.
Explicam que o masking do protocolo WebSocket foi uma tentativa de resolver problemas de intermediários.
Apontam que é estranho transmitir dados de vídeo sem compressão.
Dizem que a abordagem inicial de transmitir vídeo bruto por WebSocket é surpreendente.
Desde o começo, desenvolveram isso do jeito errado...
“Dizer que a abordagem inicial de transmitir vídeo bruto por WebSocket é surpreendente.” Concordo com isso.