Que preço pagamos pelo uso de assíncrono / aguardar em JS / C # / Rust

Olá Habr!


Ao trabalhar com Javascript / Typescript, notei por um longo tempo que a API assíncrona é mais lenta que a API síncrona semelhante, e até sabia que deveria ser. Mas no último projeto, o trabalho assíncrono com o sistema de arquivos se tornou um gargalo e eu cuidei das medidas.


Sabe-se que wait pode ser usado apenas dentro de funções ou blocos assíncronos , o que significa que, se tivermos o nível mais baixo da API assíncrono, você precisará usar async / waitit quase em todos os lugares, mesmo onde obviamente não seja necessário.


Por exemplo, escrevemos uma função de serviço que pega um objeto do armazenamento por chave. Como armazenamento, podemos usar um arquivo, banco de dados, microsserviço, ou seja, uma fonte lenta com uma interface assíncrona. Para melhorar o desempenho, dentro de nossa função, armazenamos em cache objetos extraídos anteriormente (adicione-os ao Mapa ). À medida que o programa de acessos reais à loja fica menor, os objetos são retornados do cache rápido, mas a interface da função permanece assíncrona!


Qual preço eu tenho que pagar por cada chamada assíncrona?
Os resultados dos testes são deprimentes ...


Pegue uma função simples, marque-a como assíncrona , e a chamaremos em loop, medindo o tempo total e comparando-o com um código síncrono semelhante. Para comparar a sintaxe, cito os textos completos em 3 idiomas.


Texto datilografado ( Deno )


Código síncrono
const b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') function f(i: number): number { return i / 3.1415926 } 

Código assíncrono:


 (async () => { const b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += await f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') })() async function f(i: number): Promise<number> { return i / 3.1415926 } 

C # (.NET Core)


Código síncrono
 using System; class App { static void Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static double f(long i) { return i / 3.1415926; } } 

Código assíncrono:


 using System; using System.Threading.Tasks; class App { static async Task Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += await f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static async Task<double> f(long i) { return i / 3.1415926; } } 

Ferrugem


Código síncrono
 fn main() { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i); } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); } fn f(i: i64) -> f64 { return i as f64 / 3.1415926 } 

Código assíncrono:


 //[dependencies] //futures = "0.3" use futures::executor::block_on; fn main() { block_on(async { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i).await; } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); }); } async fn f(i: i64) -> f64 { return i as f64 / 3.1415926 } 

Resultados


LinguagemCódigo síncrono (seg.)Código assíncrono (seg.)%% perda
Texto datilografado7,4817323 vezes
C #7.4676,210 vezes
Ferrugem7,4519,22,6 vezes

Vemos que a aritmética nas três línguas é igualmente boa, mas a sobrecarga por aguardar difere em uma ordem de magnitude. Curiosamente, onde o uso de assíncrono / espera é o mais comum (e até propagandizado), o custo de uma chamada assíncrona é simplesmente proibitivo. A Rust venceu a corrida como sempre, talvez essa seja a principal razão pela qual a estrutura da WEB escrita nela ganhou consistentemente pontos de referência por mais de um ano.


Sumário


Não sem razão, os desenvolvedores Java não têm pressa em adicionar sintaxe assíncrona diretamente à linguagem e, embora eu acredite que async / waitit seja uma grande abstração, precisamos entender a escala da sobrecarga ao usá-la.


PS
Obrigado a todos que apontaram a possibilidade de acelerar o código armazenando em cache tarefas / promessas (em vez de armazenar em cache os resultados), bem como a presença em C # de uma ótima ferramenta que resolve o meu problema.

Source: https://habr.com/ru/post/pt483734/


All Articles