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.
Código síncronoconst 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:
Resultados
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.