3× Mais Conexões. Mesmo Núcleo. Por Trás da Pesquisa de Performance do Engine de Accept da FlashProxy

Como a FlashProxy usou um otimizador de IA autônomo para descobrir um engine de accept TCP que usa 3× menos instruções de CPU que Go. Open-source e licenciado sob MIT.
Existe uma classe de problemas de performance que só aparece em escala, e é profundamente humilhante quando acontece. Você não está limitado pela sua lógica de roteamento. Não pela criptografia. Não por nada que sua aplicação deveria estar fazendo. Você está limitado pela infraestrutura ao redor dela. Pela tubulação.
Exatamente isso foi o que aconteceu conosco.
Por volta de 40.000 conexões por segundo, nosso proxy Go em produção começou a atingir limites de CPU no caminho de accept. O culpado era o modelo padrão do Go para serviços de rede: uma goroutine por conexão, uma chamada de sistema por operação. Toda conexão de vida curta (uma verificação de saúde, uma sonda de balanceador de carga, um pequeno redirecionamento) toca o kernel quatro vezes. accept, read, write, close. Com grande rotatividade de conexões, essas quatro idas e vindas deixam de ser overhead e se tornam o custo inteiro. O código da aplicação é quase gratuito. Colocar bytes dentro e fora do kernel não é.
A solução que todos procuram é io_uring, a interface de I/O assíncrona do Linux que agrupa operações de kernel e reduz drasticamente o número de vezes que você precisa cruzar a fronteira usuário/kernel. Sabíamos disso. A pergunta mais difícil era como ajustá-la.
Por Que Não Apenas Ajustamos Nós Mesmos
io_uring tem uma longa lista de otimizações documentadas: multishot accept, descritores de arquivo registrados, DEFER_TASKRUN, completion chains, MSG_MORE coalescing. O problema é que essas técnicas interagem umas com as outras de formas genuinamente difíceis de prever. Algumas combinações se potencializam. Algumas se cancelam mutuamente. Algumas regressões são completamente invisíveis até que você faz benchmark sob carga realista em hardware real.
Um desenvolvedor sentado com perf stat pode testar um punhado de combinações em um dia. Mas o espaço de busca real, cobrindo combinações, ordenações, valores de parâmetros e interações de versão de kernel, é muito maior que isso. Mais importante ainda, a intuição humana é um risco aqui. Tendemos a nos apegar a teorias que parecem corretas no papel, mesmo quando os dados dizem o contrário.
Então construímos um loop de medição fechado e passamos o problema de busca para um agente de IA.
Como o Loop de Pesquisa Funcionou
Configuramos duas implementações de servidor rodando lado a lado. O controle era um servidor Go modelando exatamente o caminho de accept do nosso proxy em produção: goroutine por conexão, fan-out SO_REUSEPORT. Foi congelado durante todo o experimento. O tratamento era um servidor C usando liburing, começando a partir de um loop básico de io_uring single-shot accept. Era o único código que podia ser alterado.
Ambos os servidores cumpriam o mesmo contrato: aceitar uma conexão, ler bytes de requisição, escrever uma resposta HTTP 200 fixa, fechar. Mesmo hardware, mesmo kernel, mesma carga. A única variável era como eles lidavam com isso.
Claude Code rodou headless como o otimizador. A cada iteração, ele lia o campeão atual, o histórico completo de mutações anteriores armazenado como commits git, uma base de conhecimento de lições acumuladas ao longo das execuções, e um banco de dados de dados de profiling. Ele formava uma única hipótese e fazia sua edição. Depois entregava completamente o controle.
Um harness bash separado, que a IA não podia tocar, construía a mutação, a fixava a um núcleo de CPU isolado, rodava o gerador de carga e pontuava o resultado. A fórmula de pontuação era:
score = 1.000.000.000/média(instruções por conexão)
Contagens de instrução de CPU são uma medição exata de hardware. Ao contrário dos números de throughput, elas são imunes a throttling térmico e variação de frequência de clock. Elas informam precisamente quanto trabalho a CPU está fazendo por conexão, o que é o que realmente determina a capacidade em escala.
Se uma mutação melhorava a pontuação em mais de 3% sobre o campeão atual, era promovida. Se não, era deletada com git reset --hard e o loop continuava. A IA escrevia as hipóteses. O harness tomava cada decisão de manter ou reverter. Nenhum interferia no trabalho do outro.
Para impedir que o otimizador trapaceasse no benchmark, toda execução pontuada era validada: os bytes de resposta tinham que estar exatamente corretos, as conexões tinham que ser completadas end-to-end, e a taxa de falha tinha que permanecer abaixo de 0,01%. Qualquer violação recebia pontuação zero.
O Que Encontramos
O otimizador rodou por dois dias e convergiu em seis mudanças que juntas explicam toda a lacuna de performance.
Flags de ring DEFER_TASKRUN e SINGLE_ISSUER. Estes movem o trabalho de completion task para o próprio event loop da thread de trabalho, eliminando wakeups entre CPUs. Este foi o maior salto isolado de toda a execução — uma redução direta no custo de CPU do kernel por operação, não um truque de throughput.
Descritores de arquivo registrados. Com descritores diretos, as conexões aceitas ficam na tabela do próprio ring em vez da tabela de descritores de arquivo do processo. Isso pula a instalação na fd-table no accept e a busca em todas as operações subsequentes. O otimizador testou múltiplos tamanhos de tabela e descobriu que 4.096 entradas era a configuração ideal.
Multishot accept. Em vez de rearmar a operação de accept após cada conexão, você a arma uma vez e o kernel publica um completion para cada nova conexão automaticamente. Isso reduziu as chamadas de io_uring_enter por conexão para 0,34.
Freelist de conexão por worker. Pré-alocar 128 objetos de conexão por worker eliminou malloc no caminho quente completamente. A parcela medida de instruções de CPU destinada à libc caiu de 1,32% para 0,92%.
Resposta MSG_MORE e fusão FIN. Enviar a resposta com MSG_MORE a mantém na fila de escrita TCP para que o FIN da conexão possa ser enviado junto, expedindo ambos como um único segmento TCP. Uma notificação de NIC em vez de duas. O otimizador encontrou isso ao notar que uma função de escrita de kernel de baixo nível estava consumindo o dobro da sua parcela esperada de instruções e rastreou a causa até uma divisão desnecessária de segmento.
Completions em lote com CQE_SKIP_SUCCESS. Marcar operações de send e close para que não gerem eventos de completion em caso de sucesso significa que apenas accept e receive produzem completions — aproximadamente dois por conexão em vez de quatro. Uma única chamada submit-and-wait impulsiona muitas conexões simultaneamente.
A Falha Que Ensinou Mais
Em determinado momento, o otimizador tentou encadear as operações de receive, send e close — uma técnica que faz cada uma disparar automaticamente quando a anterior é concluída. O objetivo era reduzir as idas e vindas ao kernel, e funcionou: as chamadas de enter por conexão caíram de 1,90 para 1,40.
A pontuação caiu 34%. Revertida imediatamente.
A lição que entrou na base de conhecimento: minimizar entradas no kernel não é a alavanca. A serialização em cadeia custa mais do que as entradas que economiza.
Esse é exatamente o tipo de resultado que quebra a intuição humana. A métrica que parecia ser o gargalo não era o gargalo real. Um engenheiro provavelmente teria defendido essa otimização por mais tempo. O loop a mediu, a rejeitou e seguiu em frente.
Onde a Busca Parou
Após as seis vitórias, o otimizador explorou aproximadamente uma dúzia de candidatos adicionais. Todos foram revertidos, não porque regrediram, mas porque o ruído de medição excedeu o limite de promoção de 3%. Não havia mais nada a encontrar.
Na configuração campeã, aproximadamente 94% da CPU restante pertence à pilha TCP do kernel Linux. Cerca de 1% é código de aplicação. Cerca de 4% são internals de aproveitamento. Não há código de user-space restante para otimizar de forma significativa. A pesquisa identificou corretamente o piso e parou nele.
Os Resultados
Em um núcleo fixado, limitado pela CPU, com 512 conexões in-flight fixas no loopback:
Go goroutine-per-connection: 83.250 instruções por conexão
Baseline inicial de io_uring vanilla: 59.931 instruções por conexão
Engine de accept da FlashProxy: 27.363 instruções por conexão
Isso representa 3,04× menos instruções de CPU por conexão do que Go, e 2,19× menos do que o baseline de io_uring. Em um único núcleo saturado, isso se traduz em aproximadamente seis vezes o throughput de conexão comparado ao modelo goroutine.
Estes são benchmarks de loopback projetados para isolar o custo de CPU de forma precisa. As proporções são o que importa, não os números absolutos. As técnicas subjacentes (multishot accept, DEFER_TASKRUN, descritores registrados) estão documentadas nos idiomas de io_uring. O que a pesquisa produziu foi a prova de quais combinações realmente funcionam juntas e quais parecem boas no papel mas custam caro na prática.
A Biblioteca Open Source
O design vencedor é agora flashaccept, uma biblioteca C open-source que empacota todas as seis otimizações por trás de uma API simples. Você fornece uma porta e um handler de requisição. Ela executa automaticamente um loop de accept io_uring otimizado por núcleo.
Requer Linux e liburing 2,3 ou posterior. Em kernels mais antigos, degrada gracefully. O caso de uso pretendido são conexões de alta rotatividade e vida curta: verificações de saúde, redirecionadores, sondas de balanceador de carga, pequenas respostas RPC. A plataforma de pesquisa completa — incluindo o baseline, o harness, a configuração do otimizador e a base de conhecimento completa acumulada — está incluída e é totalmente reproduzível.
Licenciado sob MIT. Disponível agora em github.com/thealonlevi/flashaccept.
Esta pesquisa veio diretamente da construção da infraestrutura proxy da FlashProxy. Se você está rodando um serviço Linux de alta rotatividade e o caminho de accept é seu gargalo, construímos isso exatamente para esse problema. Se você quiser estendê-lo ou rodar a plataforma de pesquisa você mesmo, o repositório tem tudo o que você precisa.
FAQ
O que é Flashaccept?
Flashaccept é uma biblioteca C open-source construída pela FlashProxy que fornece um engine de accept TCP de alta performance para Linux. Usa io_uring internamente e aceita conexões com 3,04× menos instruções de CPU do que um servidor Go padrão goroutine-per-connection. É licenciado sob MIT e disponível no GitHub.
Para quais cargas de trabalho é projetado?
Flashaccept é construído para conexões de alta rotatividade e vida curta onde o ciclo request-reply-close acontece em alto volume: endpoints de verificação de saúde, sondas de balanceador de carga, redirecionadores HTTP e pequenas respostas RPC. Não é projetado para conexões keep-alive ou sessões multi-exchange na v1.
Qual versão do Linux e do liburing preciso?
Você precisa do Linux com liburing versão 2,3 ou posterior, que vem com Ubuntu 24,04 e posterior. O caminho rápido (multishot accept, descritores diretos) requer kernel 5,19 ou mais novo. Em kernels mais antigos, a biblioteca recai gracefully para accept single-shot e descritores de arquivo regulares.
O que é io_uring e por que importa para a performance de proxy?
io_uring é uma interface do kernel Linux introduzida no kernel 5,1 que permite que aplicações submetam e recebam operações de I/O de forma assíncrona usando ring buffers de memória compartilhada, reduzindo drasticamente o número de chamadas de sistema necessárias. Para uma infraestrutura proxy que lida com dezenas de milhares de conexões de vida curta por segundo, o custo de cruzar repetidamente a fronteira usuário/kernel torna-se a principal despesa de CPU. io_uring reduz esse custo de forma significativa.
Flashaccept é o que FlashProxy roda em produção?
A pesquisa veio diretamente do nosso trabalho de escalabilidade em produção. A infraestrutura proxy da FlashProxy opera em mais de 190 países e lida com volume de conexão substancial. A otimização do caminho de accept foi um requisito real de engenharia, não um exercício de pesquisa. flashaccept é o resultado destilado desse trabalho, disponibilizado como open source para que outros possam utilizá-lo.
Posso usar flashaccept hoje?
Sim. É versão 1,0.1, licenciado sob MIT, e disponível via GitHub, vcpkg, Conan e Arch AUR. A biblioteca foi testada sob AddressSanitizer e UBSan em mais de 400.000 conexões em todos os quatro caminhos de configuração.
Onde posso aprender mais sobre a infraestrutura da FlashProxy?
Você pode ler mais sobre como construímos e escalamos infraestrutura proxy no blog da FlashProxy ou explorar nossa rede de proxy diretamente.

