Introdução
Não existe um vencedor universal na comparação entre .NET Native AOT, JIT e ReadyToRun. Cada modelo de publicação otimiza uma parte diferente do sistema: Native AOT elimina o JIT em execução e reduz muito o tempo até a primeira resposta; ReadyToRun antecipa parte da compilação sem abandonar o runtime dinâmico; uma aplicação JIT framework-dependent pode compartilhar o runtime instalado com outros processos; e uma publicação JIT self-contained prioriza isolamento operacional. Escolher apenas pelo rótulo “CLI”, “servidor” ou “serverless” ignora as restrições que realmente controlam o resultado.
A decisão precisa começar pelo objetivo mensurável da aplicação. Tempo de startup importa para processos efêmeros, mas throughput aquecido, p95 e p99, CPU, memória residente, tamanho de distribuição, frequência de atualização, compatibilidade com código dinâmico e tempo de publicação também podem decidir o projeto. Neste artigo, eu comparo os quatro modelos com o mesmo código em .NET 10 e linux-x64, preservando os resultados brutos. A conclusão não é uma regra pronta: é uma matriz que relaciona métricas e restrições ao contexto de operação.
Este texto complementa o guia .NET Native AOT: Parte 1 — Ecossistema de Compilação. No guia conceitual, eu expliquei Roslyn, IL, ILC, trimming e compatibilidade. Aqui, o foco é corrigir simplificações comuns com um experimento reproduzível: separar aplicação framework-dependent de distribuição autocontida, separar startup de throughput e não confundir alocação gerenciada com RSS.
ℹ️ Informação: todos os números deste artigo vieram dos CSVs e JSONs do laboratório. Valores absolutos descrevem este workload, este hardware e este ambiente; as relações entre perfis são hipóteses para você testar, não garantias para outra aplicação.
Pré-requisitos
- .NET SDK 10.0.400 ou uma imagem Docker equivalente.
- Docker Desktop com containers Linux para reproduzir o ambiente Ubuntu 24.04 e o RID
linux-x64. - Conhecimento básico de publicação framework-dependent, self-contained, ReadyToRun e Native AOT.
-
uvpara executar o consolidador determinístico dos CSVs e JSONs. - Microsoft Crank e Bombardier para repetir a carga HTTP.
O que os comentários revelaram sobre a decisão AOT
Uma discussão técnica sobre o artigo conceitual expôs a principal falha das recomendações baseadas apenas no tipo da aplicação. Dizer que Native AOT serve para CLI e serverless, enquanto JIT serve para servidores longos, parece útil porque cabe em uma frase. O problema é que essa frase omite o objetivo. Uma CLI usada uma vez por mês pode não justificar o esforço de compatibilidade; um backend replicado centenas de vezes pode valorizar densidade e startup; um worker duradouro pode depender de plugins; um serviço estável de CPU pode ter um resultado diferente de um serviço com distribuição de tráfego variável.
Startup também não é sinônimo de cold start de provedor. O laboratório mede o intervalo entre iniciar um processo e receber uma resposta válida de /ready, com cache do sistema de arquivos aquecido. Um cold start de Azure Functions, AWS Lambda ou outra plataforma inclui decisões do provedor, criação de sandbox, pull de imagem, rede, configuração e inicialização da aplicação. Eu não medi esse caminho e, portanto, não atribuo ao Native AOT um ganho universal em serverless gerenciado.
Outro ponto importante é a previsibilidade. Remover o JIT evita compilação de métodos durante a execução, mas não remove pausas de GC, escalonamento do sistema operacional, contenção, I/O ou page faults. Da mesma forma, source generators reduzem descoberta dinâmica e deslocam erros para o build, mas não tornam automaticamente qualquer arquitetura mais rápida. São propriedades relevantes para a decisão, não selos de superioridade.
Minha regra revisada é mais exigente: defina uma meta, escolha as métricas capazes de refutá-la e compare distribuições equivalentes. Se o objetivo é reduzir primeira resposta, meça processos novos. Se é aumentar densidade, meça RSS sob a carga representativa. Se é sustentar throughput, aqueça a aplicação e registre latência de cauda e CPU junto com requisições por segundo.
Metodologia: como tornar a comparação justa
O experimento usa Ubuntu 24.04 em Docker Desktop, processador Intel Core i5-1245U x64, aproximadamente 8 GB de memória disponíveis, SDK 10.0.400 e runtime 10.0.11. Todos os perfis executam em Release, ambiente Production, sem providers de logging e com o mesmo payload. O código não acessa banco, disco ou serviço externo durante a medição.
O workload recebe Seed=42, ItemCount=2048 e Iterations=8. Cada execução aluga um array com ArrayPool<int>, gera valores determinísticos, ordena os dados, calcula média e percentil 95 e produz um SHA-256. A Minimal API serializa a resposta com System.Text.Json source generation. Essa combinação exerce CPU, alocação, genéricos e serialização sem deixar I/O externo dominar.
Os nomes combinam o modo de compilação com o modelo de distribuição:
- JIT (Just-In-Time) compila IL em código nativo durante a execução.
- FDD (Framework-Dependent Deployment) publica somente a aplicação e exige um runtime .NET compatível instalado no host.
- SCD (Self-Contained Deployment) inclui o runtime .NET no diretório publicado.
- R2R (ReadyToRun) antecipa a compilação de assemblies elegíveis, mas preserva runtime e JIT.
- Native AOT (Native Ahead-Of-Time) gera antes da execução um binário nativo autocontido, sem JIT em produção. Os quatro perfis são:
| Perfil | Runtime no diretório | Código antecipado | JIT em execução |
|---|---|---|---|
| JIT FDD (dependente do framework) | Não; usa instalação compatível | Bibliotecas do runtime podem conter R2R | Sim |
| JIT SCD (autocontido) | Sim | Bibliotecas do runtime podem conter R2R | Sim |
| R2R SCD (ReadyToRun autocontido) | Sim | Aplicação e dependências elegíveis | Sim |
| Native AOT (nativo autocontido) | Runtime reduzido incluído | Aplicação fechada em código nativo | Não |
O harness restaura dependências antes do cronômetro, executa três pares de publish limpo e incremental e inicia cada perfil 30 vezes em ordem randomizada. Antes de medir startup, ele copia os publishes para o filesystem Linux local do container; executar binários grandes diretamente no bind mount do Windows adicionou quase dez segundos artificiais em testes preliminares.
Para estado aquecido, o BenchmarkDotNet usa jobs out-of-process reais para .NET 10 JIT e Native AOT. A carga HTTP usa Microsoft Crank com Bombardier, 64 conexões, 15 segundos de warm-up e 30 segundos de medição. Aplicação e gerador ficam em containers separados, mas na mesma máquina física. Isso contamina a capacidade absoluta, por isso eu uso os resultados apenas como comparação relativa deste ambiente.
Exemplo Prático: laboratório reproduzível em .NET 10
A API foi construída com o host reduzido do ASP.NET Core. O marcador escrito após Start() permite ao Crank sincronizar a carga sem habilitar logs diferentes entre perfis:
WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.ClearProviders();
builder.Services.Configure<JsonOptions>(options =>
options.SerializerOptions.TypeInfoResolverChain.Insert(
0,
DecisionLabJsonContext.Default));
WebApplication app = builder.Build();
app.MapGet("/ready", () => TypedResults.Ok(new ReadyResponse("ready")));
app.MapPost("/work", (WorkloadRequest request) =>
TypedResults.Ok(WorkloadProcessor.Process(request)));
app.Start();
Console.WriteLine("Application started.");
app.WaitForShutdown();
📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog:
BlogSamples/NativeAot/DecisionLab/
O comando principal publica, mede tamanho e executa os 120 startups dentro da imagem fixada:
docker run --rm \
--volume "$PWD:/workspace" \
--volume decision-lab-nuget:/root/.nuget/packages \
decision-lab:10.0.400 \
run --project src/BlogSamples/NativeAot/DecisionLab/DecisionLab.Harness \
--configuration Release -- --repository-root /workspace
O manifesto registra commit, estado do worktree, fingerprint dos fontes e ambiente. O fingerprint é necessário porque um hash de HEAD não identifica alterações ainda não commitadas. O consolidador calcula mediana, mínimo, máximo, desvio absoluto mediano (MAD) e razão contra JIT FDD; diferenças de até 3% são marcadas como dentro do limiar operacional de ruído, sem fingir significância estatística.
Publish, tamanho e startup de processo
Native AOT deslocou custo para o build. O publish limpo mediano levou 98,145 segundos, contra 11,589 segundos do JIT FDD, 28,450 segundos do JIT SCD e 25,812 segundos do R2R SCD. No publish incremental, a distância diminuiu: Native AOT ficou em 13,398 segundos; JIT FDD, 11,679; JIT SCD, 14,552; e R2R SCD, 13,787.
| Perfil | Publish limpo mediano | Publish incremental mediano |
|---|---|---|
| JIT FDD | 11,589 s | 11,679 s |
| JIT SCD | 28,450 s | 14,552 s |
| R2R SCD | 25,812 s | 13,787 s |
| Native AOT | 98,145 s | 13,398 s |
O tamanho exige uma comparação cuidadosa. Os 165.687 bytes do JIT FDD representam somente os arquivos da aplicação; a instalação compartilhada do .NET fica fora do diretório. JIT SCD e R2R SCD incluem runtime e bibliotecas e ficaram perto de 109,7 MB. Native AOT incluiu o runtime reduzido necessário e chegou a 28,3 MB. Compactados, os diretórios ocuparam 81.471 bytes, 48,2 MB, 48,2 MB e 10,9 MB, respectivamente.
| Perfil | Unidade comparada | Descompactado | ZIP |
|---|---|---|---|
| JIT FDD | Aplicação, sem runtime instalado | 165.687 B | 81.471 B |
| JIT SCD | Distribuição completa | 109.703.386 B | 48.156.909 B |
| R2R SCD | Distribuição completa | 109.745.882 B | 48.177.185 B |
| Native AOT | Distribuição completa | 28.306.209 B | 10.865.327 B |
No startup de processo, Native AOT apresentou mediana de 63,794 ms e MAD de 8,948 ms. JIT FDD registrou 587,644 ms; JIT SCD, 633,357 ms; e R2R SCD, 585,283 ms. R2R e JIT FDD ficaram dentro do limiar operacional de 3%, portanto este experimento não sustenta declarar R2R vencedor no startup.
| Perfil | Mediana de startup | MAD | Amostras |
|---|---|---|---|
| JIT FDD | 587,644 ms | 100,856 ms | 30 |
| JIT SCD | 633,357 ms | 93,885 ms | 30 |
| R2R SCD | 585,283 ms | 79,038 ms | 30 |
| Native AOT | 63,794 ms | 8,948 ms | 30 |
⚠️ Atenção: esses números medem processo novo com cache de filesystem aquecido. Eles não medem boot da máquina, pull de container nem cold start de um provedor serverless.
Runtime compartilhado e unidades autocontidas
Framework-dependent é uma escolha eficiente quando várias aplicações usam uma instalação compatível do .NET no mesmo host. O custo do runtime não desaparece; ele é instalado, atualizado e potencialmente compartilhado fora do artefato da aplicação. Em containers isolados ou hosts sem runtime, essa premissa muda.
Cada publicação Native AOT é uma unidade autocontida e leva as partes do runtime necessárias. Não existe, no modelo de publicação documentado, um runtime Native AOT comum consumido por vários executáveis gerenciados independentes. O sistema operacional pode compartilhar páginas somente leitura entre processos do mesmo binário, mas isso é um comportamento de memória virtual e não transforma executáveis diferentes em clientes de um runtime comum.
ℹ️ Informação:
UnmanagedCallersOnlypode exportar entry points C de um módulo Native AOT para interoperabilidade nativa. Isso não cria uma DLL gerenciada AOT compartilhada entre aplicações .NET.
Throughput, latência e memória sob carga
A campanha HTTP usa o mesmo endpoint e payload nos quatro perfis. Eu registro mediana e dispersão de requisições por segundo, p50, p95, p99, CPU máxima do processo e working set máximo em cinco rodadas randomizadas por perfil. Como aplicação e Bombardier compartilham a mesma máquina física, os números não representam a capacidade máxima de produção.
As 20 rodadas terminaram com zero respostas inválidas. JIT FDD apresentou a maior mediana, 932 RPS, seguido por Native AOT com 810 RPS, JIT SCD com 803 RPS e R2R SCD com 734 RPS. A dispersão foi alta: o MAD variou de 82 RPS no JIT SCD a 240 RPS no R2R SCD, e os intervalos observados se sobrepõem. Neste host compartilhado, a ordem das medianas não sustenta uma regra geral de throughput.
| Perfil | RPS mediana | MAD RPS | p50 | p95 | p99 |
|---|---|---|---|---|---|
| JIT FDD | 932 | 130 | 63,936 ms | 136,854 ms | 194,880 ms |
| JIT SCD | 803 | 82 | 78,570 ms | 142,288 ms | 210,696 ms |
| R2R SCD | 734 | 240 | 83,483 ms | 191,594 ms | 312,714 ms |
| Native AOT | 810 | 186 | 79,518 ms | 145,527 ms | 210,021 ms |
O sinal mais nítido apareceu em recursos. Native AOT registrou mediana de 41 MB para o working set máximo, contra 103 MB no JIT FDD, 101 MB no JIT SCD e 96 MB no R2R SCD. A CPU máxima mediana foi 62% no Native AOT, 83% no JIT FDD, 79% no JIT SCD e 86% no R2R SCD. São máximos amostrados pelo Crank, não consumo médio nem memória privada.
| Perfil | CPU máxima mediana | Working set máximo mediano | MAD do working set |
|---|---|---|---|
| JIT FDD | 83% | 103 MB | 2 MB |
| JIT SCD | 79% | 101 MB | 2 MB |
| R2R SCD | 86% | 96 MB | 2 MB |
| Native AOT | 62% | 41 MB | 1 MB |
ℹ️ Informação: nesta campanha, Native AOT usou cerca de 40% do working set máximo mediano do JIT FDD. O resultado é específico deste workload AOT-friendly e não prevê o RSS de outra aplicação.
JIT, Dynamic PGO e Native AOT no código quente
ReadyToRun não elimina o JIT. Com tiered compilation habilitada, métodos usados com frequência podem ter o código R2R substituído por código produzido pelo optimizing JIT. Dynamic PGO trabalha junto com esse mecanismo: instrumenta a execução, observa tipos e caminhos quentes e usa esses dados para recompilar métodos relevantes.
Native AOT não dispõe de JIT em produção, então não pode adaptar o código durante a execução da mesma forma. Isso é uma diferença de capacidade, não uma prova de que JIT sempre vence. Código AOT evita warm-up de compilação e pode empatar ou superar o JIT em workloads específicos; o JIT pode se beneficiar quando o processo dura o suficiente e a distribuição observada orienta otimizações úteis.
No SDK 10.0.400 e nas fontes públicas consultadas, eu não confirmei um fluxo estável e documentado de PGO de build para Native AOT que pudesse ser recomendado como procedimento de produção neste laboratório. Por isso, a comparação usa os defaults suportados e não inclui opções experimentais. Mesmo quando um perfil offline estiver disponível, ele precisa representar produção: mudanças de tipos, rotas ou distribuição de tráfego podem envelhecer a evidência coletada.
Os dois jobs concluídos pelo mesmo BenchmarkDotNet produziram uma mediana de 1,034 ms para JIT e 1,638 ms para Native AOT. Neste workload aquecido, Native AOT levou 1,585 vez o tempo do baseline JIT. As alocações gerenciadas ficaram próximas: 8.984 bytes por operação no JIT e 9.370 bytes no Native AOT, diferença de 4,3%.
| Runtime | Mediana | MAD | Amostras | Alocação por operação |
|---|---|---|---|---|
| .NET 10 JIT | 1,034 ms | 0,133 ms | 92 | 8.984 B |
| Native AOT 10 | 1,638 ms | 0,161 ms | 99 | 9.370 B |
O BenchmarkDotNet marcou a distribuição Native AOT como bimodal (mValue = 3,25) e removeu um outlier; no JIT, removeu oito outliers. Portanto, o resultado refuta a hipótese de empate neste ensaio, mas não demonstra que JIT sempre vence código quente. Ele mostra apenas que, para esta carga, neste ambiente e com os defaults suportados, a adaptação disponível no runtime JIT coincidiu com menor duração mediana.
Source generators e reflection mudam o custo
O workload usa System.Text.Json source generation. O contexto JSON registra os contratos usados pela API, permitindo que o compilador veja quais metadados precisam ser preservados. Isso reduz descoberta baseada em reflection e torna o cenário deliberadamente compatível com Native AOT.
Reflection não é um bloco único. Acesso analisável a metadados ou membros preservados pode funcionar com anotações e testes. Os problemas mais fortes aparecem quando o conjunto de código só é conhecido em execução: Assembly.LoadFile, Reflection.Emit, plugins abertos e serializers que descobrem tipos arbitrários. O trimmer precisa enxergar o grafo no build; caso contrário, ele não consegue provar o que pode remover.
Evitar código dinâmico pode ser uma decisão arquitetural saudável, principalmente quando source generators e contratos explícitos antecipam erros. Também pode ser inviável para um produto cujo valor depende de extensibilidade aberta. Compatibilidade deve ocupar uma linha própria na matriz de decisão; não deve ser tratada como detalhe resolvido por um atributo genérico que preserva tudo.
Onde as conclusões não se aplicam diretamente
Hard real-time exige previsibilidade de ponta a ponta. Ausência de JIT não controla pausas de GC, escalonamento do sistema operacional, interrupções, I/O, locks ou page faults. Soft real-time, por outro lado, pode funcionar com um processo JIT aquecido e medido. Este laboratório não mede deadlines nem usa um sistema operacional de tempo real.
Blazor WebAssembly AOT é outro pipeline. Ele compila código .NET para WebAssembly executado pelo navegador e normalmente troca um download maior por melhor desempenho em trechos intensivos de CPU. Não é o mesmo modelo de executável Native AOT para Linux, Windows ou macOS. Blazor Server se aproxima mais da decisão de um backend ASP.NET Core, mas recursos e dependências precisam ser avaliados separadamente.
Também não há resultados de preview do .NET 11, bibliotecas Native AOT exportadas, provedores serverless ou aplicações com banco de dados. Incluir esses cenários sem medi-los aumentaria o alcance aparente e reduziria a utilidade da conclusão.
Matriz de decisão orientada ao objetivo
| Objetivo ou restrição | Pergunta de medição | Opções que merecem baseline |
|---|---|---|
| Primeira resposta | Quanto leva processo novo até responder? | Native AOT, R2R e JIT |
| Throughput aquecido | Qual perfil sustenta RPS com p95/p99 aceitáveis? | JIT com tiering/PGO e Native AOT |
| Memória e densidade | Qual é o RSS sob carga por réplica? | Native AOT, JIT FDD e JIT SCD |
| Distribuição | Quantos bytes são transferidos e atualizados? | FDD se runtime existe; AOT/SCD se não existe |
| Runtime compartilhado | Várias aplicações usam o mesmo host administrado? | JIT FDD |
| Plugins e código dinâmico | O grafo de código é conhecido no build? | JIT; validar trimming antes de AOT |
| Tempo de build | A frequência de entrega tolera publish AOT? | JIT/R2R como baseline |
| Operação | Ferramentas de diagnóstico cobrem o modo escolhido? | Validar por ambiente e incidente esperado |
Uma CLI distribuída para máquinas sem .NET pode valorizar Native AOT por startup e empacotamento. Muitas aplicações em um host administrado podem aproveitar FDD. Um backend duradouro e variável pode se beneficiar da adaptação do JIT. Edge e IoT podem priorizar bytes, memória e restrições de execução. Esses exemplos orientam quais testes executar; nenhum substitui a medição.
Limitações do experimento
O hardware é um notebook com Docker Desktop 4.81.0, e aplicação e gerador de carga disputam a mesma CPU. O RID é apenas linux-x64, o sistema é Ubuntu 24.04 e o plano de energia Equilibrado foi observado durante a campanha. Isso não controla frequência, temperatura nem tarefas de fundo do host, que podem aumentar a dispersão. A campanha usa cache de filesystem aquecido e não reinicia o sistema operacional entre amostras.
O workload é sintético, determinístico e deliberadamente AOT-friendly. Ele não representa acesso a banco, rede externa, plugins, descoberta aberta de tipos nem uma base de código grande. A memória reportada pelo Crank é working set máximo, enquanto o BenchmarkDotNet registra alocação gerenciada por operação; essas métricas respondem perguntas diferentes.
Três publishes e 30 startups permitem observar dispersão operacional, mas não transformam o laboratório em estudo universal. As cinco rodadas HTTP ajudam a detectar instabilidade, e o limiar de 3% evita anunciar diferenças pequenas como vencedores. Ainda assim, o limiar não é um teste formal de significância.
Dicas e Boas Práticas
Comece com um baseline simples. Compare primeiro a publicação padrão que sua equipe já opera. Native AOT e ReadyToRun precisam melhorar uma métrica relevante o suficiente para pagar o custo de compatibilidade, build e manutenção.
Execute performance em Release e Production. Debug e ambiente Development adicionam trabalho e configuração que distorcem resultados. Mantenha logging, GC, payload e funcionalidade iguais entre os perfis sempre que o modo de publicação permitir.
Meça distribuições, não uma única execução. Use processos novos para startup, warm-up explícito para steady state e ordem randomizada. Registre mediana e dispersão para não confundir ruído térmico, cache ou tarefa de fundo com efeito do compilador.
Separe tamanho de aplicação e tamanho de distribuição. FDD app-only pressupõe runtime instalado; SCD e Native AOT incluem runtime. Declare a unidade comparada e, quando transferência importar, meça também o artefato compactado.
Preserve dados brutos e ambiente. CSV, JSON, manifesto, commit e fingerprint dos fontes tornam cada tabela auditável. Copiar números manualmente para Markdown sem rastreabilidade facilita erros de unidade e arredondamento.
Trate respostas inválidas como falha do teste. RPS alto com HTTP 400, 500 ou timeout não é desempenho da aplicação. Automatize um gate que exija zero respostas ruins antes de consolidar a rodada.
Acompanhe regressões no CI com tolerância ao ruído. Um benchmark isolado não impede que dependências ou mudanças de workload alterem o resultado. Use uma máquina estável e só alerte quando a diferença superar a variabilidade histórica do cenário.
Resumo Objetivo
- Decisão entre JIT, ReadyToRun e Native AOT — não existe vencedor universal; primeira resposta, throughput, latência de cauda, CPU, memória, distribuição e compatibilidade respondem perguntas diferentes.
- Startup Native AOT neste ambiente — a mediana foi 63,794 ms em 30 processos, contra 587,644 ms no JIT FDD; a medição usa cache de filesystem aquecido e não representa cold start de provedor.
- Publish Native AOT neste ambiente — o publish limpo mediano levou 98,145 s, enquanto o incremental levou 13,398 s; o custo mais alto concentrou-se na compilação limpa.
- Tamanho JIT FDD — os 165.687 bytes medidos cobrem somente a aplicação e pressupõem um runtime compatível instalado, portanto não são diretamente comparáveis às distribuições autocontidas completas.
- Tamanho Native AOT — a distribuição completa ficou em 28.306.209 bytes descompactados e 10.865.327 bytes em ZIP, incluindo as partes necessárias do runtime sem JIT.
- ReadyToRun — mantém IL, runtime, JIT e tiered compilation; neste startup, R2R SCD e JIT FDD ficaram dentro do limiar operacional de 3%.
- Carga HTTP neste ambiente — JIT FDD teve mediana de 932 RPS, Native AOT 810, JIT SCD 803 e R2R SCD 734; Native AOT registrou 41 MB de working set máximo mediano contra 103 MB no JIT FDD, mas a alta dispersão impede generalizar a capacidade.
- Código quente neste workload — a mediana foi 1,034 ms no JIT e 1,638 ms no Native AOT; o relatório marcou a distribuição AOT como bimodal, portanto a conclusão permanece restrita ao ensaio.
Leia Também
- .NET Native AOT: Parte 1 — Ecossistema de Compilação
- Paralelismo em C#: Parallel, Tasks e PLINQ
- .NET Worker com BackgroundService para Alto Volume
- C# como Script no .NET: Como Usar e Quando Vale a Pena
Referências
- Native AOT deployment overview — modelo de publicação, requisitos, runtime reduzido e limitações oficiais.
- Native code interop with Native AOT — P/Invoke direto e exports C com
UnmanagedCallersOnly. - ReadyToRun deployment overview — formato R2R, impacto no tamanho e interação com tiered compilation.
- Compilation config settings — tiered compilation, ReadyToRun e dynamic PGO.
- Known trimming incompatibilities — serializers por reflection, geração dinâmica e carregamento de assemblies.
- Blazor WebAssembly AOT — pipeline WebAssembly e trade-off entre download e execução.
- BenchmarkDotNet NativeAOT — jobs e toolchains out-of-process para runtimes distintos.
- Microsoft Crank — controller, agents e jobs de carga usados pelo time .NET.
- ASP.NET Core load and stress testing — orientação para executar carga em Release e Production. 📬
👉 Artigo completo com todos os exemplos de código: .NET Native AOT: Parte 2 — JIT, R2R e AOT em Benchmarks
Top comments (0)