Um benchmark de tempo de resposta que compara as versões de ASP.NET 4.8 e ASP.NET CORE 3.0 só reafirma o que eu já suspeitava.
Quando começei a desenvolver com ASP.NET CORE percebi algumas mudanças na performance de tempo de resposta em algumas funcionalidades comparando com as mesmas no ASP.NET FRAMEWORK.
Pensei comigo, preciso fazer um benchmark sobre isso comparando esses dois frameworks.
Mas eu não queria fazer algo muito complexo, com dados de memória, processador e tal. Queria algo que não fosse muito difícil de entender.
Algumas informações importantes para quem está chegando agora. Se você é um desenvolvedor .NET mas nunca viu nada de .NET Core, sugiro você ler antes o artigo .NET Core para DesenvolvedoreS .NET.
Eu não usei o BenchmarkDotNet!
Conversei com um amigo a respeito desse artigo e que me sugeriu o uso da lib BenchmarkDotNet, no qual você instala em sua aplicação através do NuGet, enche as classes, propriedades e método de atributos.
Ela roda seu código nos CLR/Framework que você escolher e te dá um resultado detalhado. Para conhecer mais sobre ela clique aqui.
Essa lib é muito interessante, mas não é o foco desse artigo. Eu não queria nada automatizado e que não usasse nada já pronto de mercado.
As premissas foram as seguintes:
– Usar máquina de desenvolvedor
– Usar template padrão do Visual Studio 2019
– Aplicação apenas com Web API (excluir todo o resto)
– Foco em apenas tempo de resposta (em milesegundo)
– Todos os pacotes NuGet devem estar atualizados
– Instalar NuGet Newtonsoft JSON
– Instalar NuGet Dapper (SQL Server)
– Testes em IIS Express / Modo debug
– Usar Postman para os requests
– Sem alteração/manipulação do código fonte. Ou seja, desenvolvimento normal e mundo real.
– As tabelas de endereços do SQL Server possuem mais de 1M de registros.
Os tempos de resposta foram colhidos da seguinte forma:
1 – Executar a aplicação web.
2 – Abrir o Postman.
3 – Fazer o request.
4 – Pegar o resultado do tempo de resposta (em milesegundo).
5 – Realizar três requests.
Eu também acrescentei uma aplicação ASP.NET CORE 2.2 como base de comparação.
Código fonte
// ASP.NET 4.8 public sealed class AddressSqlRepository { public async Task<Address> GetAddressAsync( string zipCode) { using (var connection = CreateConnection()) { await connection.OpenAsync(); var parameters = new { zipCode }; var sql = @"SELECT a.cod_postal AS ZipCode, b.des_cidade AS City, c.des_sigla AS State, (t.des_tipo_logradouro + ' ' + d.des_logradouro) AS Street, r.des_bairro AS Neighborhood FROM dbo.tb_cep AS a LEFT OUTER JOIN dbo.tb_cidade AS b ON a.cod_cidade = b.cod_cidade LEFT OUTER JOIN dbo.tb_estado AS c ON a.cod_estado = c.cod_estado LEFT OUTER JOIN dbo.tb_logradouro AS d ON a.cod_logradouro = d.cod_logradouro LEFT OUTER JOIN dbo.tb_tipo_logradouro AS t ON a.cod_tipo_logradouro = t.cod_tipo_logradouro LEFT OUTER JOIN dbo.tb_bairro AS r ON a.cod_bairro = r.cod_bairro WHERE a.cod_postal = @zipCode"; var data = await connection.QueryFirstOrDefaultAsync<Address>( sql, parameters); connection.Close(); return data; }; } public async Task<IEnumerable<Address>> GetAddressRangeAsync( string start, string end) { using (var connection = CreateConnection()) { await connection.OpenAsync(); var parameters = new { start, end }; var sql = @"SELECT a.cod_postal AS ZipCode, b.des_cidade AS City, c.des_sigla AS State, (t.des_tipo_logradouro + ' ' + d.des_logradouro) AS Street, r.des_bairro AS Neighborhood FROM dbo.tb_cep AS a LEFT OUTER JOIN dbo.tb_cidade AS b ON a.cod_cidade = b.cod_cidade LEFT OUTER JOIN dbo.tb_estado AS c ON a.cod_estado = c.cod_estado LEFT OUTER JOIN dbo.tb_logradouro AS d ON a.cod_logradouro = d.cod_logradouro LEFT OUTER JOIN dbo.tb_tipo_logradouro AS t ON a.cod_tipo_logradouro = t.cod_tipo_logradouro LEFT OUTER JOIN dbo.tb_bairro AS r ON a.cod_bairro = r.cod_bairro WHERE a.cod_postal BETWEEN @start AND @end"; var data = await connection.QueryAsync<Address>( sql, parameters); connection.Close(); return data; }; } private SqlConnection CreateConnection() { return new SqlConnection(ConfigurationManager.ConnectionStrings["Default"].ConnectionString); } } // ASP.NET CORE public sealed class AddressSqlRepository { private readonly IConfiguration _configuration; public AddressSqlRepository( IConfiguration configuration) { _configuration = configuration; } public async Task<Address> GetAddressAsync( string zipCode) { using (var connection = CreateConnection()) { await connection.OpenAsync(); var parameters = new { zipCode }; var sql = @"SELECT a.cod_postal AS ZipCode, b.des_cidade AS City, c.des_sigla AS State, (t.des_tipo_logradouro + ' ' + d.des_logradouro) AS Street, r.des_bairro AS Neighborhood FROM dbo.tb_cep AS a LEFT OUTER JOIN dbo.tb_cidade AS b ON a.cod_cidade = b.cod_cidade LEFT OUTER JOIN dbo.tb_estado AS c ON a.cod_estado = c.cod_estado LEFT OUTER JOIN dbo.tb_logradouro AS d ON a.cod_logradouro = d.cod_logradouro LEFT OUTER JOIN dbo.tb_tipo_logradouro AS t ON a.cod_tipo_logradouro = t.cod_tipo_logradouro LEFT OUTER JOIN dbo.tb_bairro AS r ON a.cod_bairro = r.cod_bairro WHERE a.cod_postal = @zipCode"; var data = await connection.QueryFirstOrDefaultAsync<Address>( sql, parameters); await connection.CloseAsync(); return data; }; } public async Task<IEnumerable<Address>> GetAddressRangeAsync( string start, string end) { using (var connection = CreateConnection()) { await connection.OpenAsync(); var parameters = new { start, end }; var sql = @"SELECT a.cod_postal AS ZipCode, b.des_cidade AS City, c.des_sigla AS State, (t.des_tipo_logradouro + ' ' + d.des_logradouro) AS Street, r.des_bairro AS Neighborhood FROM dbo.tb_cep AS a LEFT OUTER JOIN dbo.tb_cidade AS b ON a.cod_cidade = b.cod_cidade LEFT OUTER JOIN dbo.tb_estado AS c ON a.cod_estado = c.cod_estado LEFT OUTER JOIN dbo.tb_logradouro AS d ON a.cod_logradouro = d.cod_logradouro LEFT OUTER JOIN dbo.tb_tipo_logradouro AS t ON a.cod_tipo_logradouro = t.cod_tipo_logradouro LEFT OUTER JOIN dbo.tb_bairro AS r ON a.cod_bairro = r.cod_bairro WHERE a.cod_postal BETWEEN @start AND @end"; var data = await connection.QueryAsync<Address>( sql, parameters); await connection.CloseAsync(); return data; }; } private SqlConnection CreateConnection() { return new SqlConnection(_configuration.GetConnectionString("Default")); } }
#1 Benchmark – Lista de Endereços
Trazer 20 registros de endereços em uma consulta a banco de dados SQL Server usando Dapper.
Vencedor: ASP.NET 4.8 (19 ms)
#2 Benchmark – Um Endereço
Trazer 1 registro (PK) de endereço em uma consulta a banco de dados SQL Server usando Dapper.
Vencedor: ASP.NET 4.8 (14 ms)
#3 Benchmark – 97.996 endereços TXT
Trazer 97.996 registros de endereços em uma consulta a banco de dados SQL Server usando Dapper e transformá-los em TXT.
Foram utilizados recursos de String Builder, Reflection e LINQ.
Vencedor: ASP.NET 4.8 e ASP.NET CORE 3.0 (1.172 ms)
#4 Benchmark – 477.397 endereços TXT
Foi realizado o mesmo teste que o anterior, só que nesse caso trazendo metade das tabelas de endereços.
Vencedor: ASP.NET CORE 3.0 (5.344 ms)
public async Task<string> GetRangeTxtAsync( string start, string end) { var addresses = await _addressRepository.GetAddressRangeAsync( start, end); string txt; if (addresses == null || addresses.Count() == 0) { txt = "addresses NULL or EMPTY"; } else { var sb = new StringBuilder(); sb.Append(GetColumns<Address>()); sb.Append("\r\n"); foreach (var address in addresses) { sb.Append(GetColumns(address)); sb.Append("\r\n"); } txt = sb.ToString(); } return txt; } private string GetColumns<T>( T data = default) { var sb = new StringBuilder(); var type = typeof(T); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo property in properties) { if (sb.Length > 0) { sb.Append(";"); } if (data == null) { sb.AppendFormat( "{0}_{1}", type.Name, property.Name); } else { var val = property.GetValue(data); sb.Append(val == null ? "" : $" {val.ToString()}"); } } sb.Append(";"); return sb.ToString(); }
#5 Benchmark – 97.996 endereços JSON
Trazer 97.996 registros de endereços em uma consulta a banco de dados SQL Server usando Dapper e usando serialização JSON nativa da versão do framework.
Vencedor: ASP.NET CORE 2.2 (1.082 ms)
#6 Benchmark – 477.397 endereços JSON
Foi realizado mesmo teste que anterior mas nesse caso trazendo metade da tabela de endereços.
Vencedor: ASP.NET CORE 3.0 (5.023 ms)
#7 Benchmark – Ler arquivo PDF 7MB
Foi colocado um arquivo na pasta App_Data com cerca de 7MB e retornado no response como download.
Vencedor: ASP.NET CORE 2.2 (84 ms)
// ASP.NET 4.8 public HttpResponseMessage GetFileSystemDownload() { var path = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/Cartilha_do_Idoso.pdf"); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(new FileStream(path, FileMode.Open, FileAccess.Read)) }; response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = Path.GetFileName(path) }; response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); return response; } // ASP.NET CORE public IActionResult GetFileSystemDownload() { var path = $@"{_env.ContentRootPath}\App_Data\Cartilha_do_Idoso.pdf"; return new FileStreamResult(new FileStream(path, FileMode.Open, FileAccess.Read), "application/pdf"); }
#8 Benchmark – Ler arquivos c:\windows
Usando System.IO, trazer a lista de arquivos da pasta Windows.
Vencedor: ASP.NET 4.8 (11 ms)
Considerações
Eu repeti esses testes outras três vezes em dias e horários diferentes e todos os valores ficaram na média, ou seja, os resultados finais foram os mesmos.
E se não fosse o bastante, peguei o projeto e rodei os testes em outra máquina de desenvolvimento e os resultados finais foram os mesmos.
O mais interessante desse meu benchmark é que, para alguns casos o ASP.NET 4.8 é melhor que o APS.NET CORE (3.0/2.2) e vice-versa.
Se você conhece algum outro benchmark comenta aí que atualizo esse artigo e fica a pergunta, para você qual o melhor framework?
Obrigado 🙂
Artigos sobre ASP.NET CORE:
Crie seu Framework em ASP.NET CORE 3 e Blazor
.NET Core para Desenvolvedores .NET
IIS: Como Hospedar Aplicação .NET Core em 10 Passos
Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS
JWT: Customizando o Identity no ASP.NET CORE 3.0
AppSettings: 6 Formas de Ler o Config no ASP.NET CORE 3.0
Índice Benchmark
ASP.NET Core: Saturating 10GbE at 7+ million request/s
Performance Improvements in .NET Core 3.0
Performance Tests / Benchmarking for ASP.NET Core 2.2 Endpoints
Dicas de performance para APIs REST no ASP.NET Core
Teste de performance de aplicações .NET Core com BenchMarkDotNet
Swift vs .NET Core — Benchmark
Dapper vs EF Core Query Performance Benchmarking
Lightweight .NET Core benchmarking with BenchmarkDotNet and dotnet-script
.NET Serialization Benchmark 2019 Roundup
Benchmarking .NET code
Profiling .NET Code with PerfView and visualizing it with speedscope.app
Benchmarking Your .NET Core Code With BenchmarkDotNet
Performance benchmark: gRPC vs. REST in .NET Core 3 Preview 8
The Battle of C# to JSON Serializers in .NET Core 3
C# .NET Core versus Java fastest programs
gRPC performance benchmark in ASP.NET Core 3
Faça download completo do código fonte no github. |