Como em qualquer conversa sobre desempenho, precisamos obter algum contexto compartilhado em torno do tipo de código JavaScript que desejamos otimizar e o contexto no qual ele será executado. Então, vamos começar com algumas definições:

Desempenho. Em primeiro lugar, quando usamos a palavra desempenho no contexto de um programa de computador, estamos nos referindo à rapidez e eficiência com que esse programa pode ser executado.

Funções polimórficas. Uma função polimórfica é uma função que muda seu comportamento com base nos tipos de argumentos que são passados ​​a ela.

A palavra-chave aqui é tipos, em oposição a valores. (Uma função que não mudou sua saída com base em diferentes valores para argumentos não seria uma função muito útil.)

Mecanismo JavaScript. Para pensar sobre o desempenho de forma produtiva, também precisamos saber onde nosso JavaScript será executado. Para nosso código de exemplo, usaremos o motor V8 devido à sua popularidade.

V8 é o mecanismo que alimenta o navegador Chrome, Node.js, o navegador Edge e muito mais. Observe que também existem outros mecanismos JavaScript com suas próprias características de desempenho, como SpiderMonkey (usado pelo Firefox), JavaScriptCore (usado pelo Safari) e outros.

Criação de uma função polimórfica em JavaScript

Suponha que estejamos construindo uma biblioteca JavaScript que permite a outros engenheiros armazenar facilmente mensagens em um banco de dados na memória com nossa API simples. Para tornar nossa biblioteca o mais fácil e confortável possível de usar, fornecemos uma única função polimórfica que é muito flexível com os argumentos que recebe.

Opção 1: Use argumentos completamente separados

A primeira assinatura de nossa função pegará os dados necessários como três argumentos separados e pode ser chamada assim:

 saveMessage (autor, conteúdo, timestamp);

Opção 2: usar o conteúdo da mensagem com o objeto options

Esta assinatura permitirá que os consumidores separem os dados necessários (conteúdo da mensagem) dos dados opcionais (o autor e o carimbo de data/hora) em dois argumentos separados. Aceitaremos os argumentos em qualquer ordem, por conveniência.

 saveMessage (conteúdo, opções);
saveMessage (opções, conteúdo);

Opção 3: Use um objeto options

Também permitiremos que os usuários de nossa API chamem a função passando um único argumento de um objeto contendo todos os dados de que precisamos:

 saveMessage (opções);

Opção 4: Use apenas o conteúdo da mensagem

Por fim, permitiremos que os usuários de nossa API forneçam apenas o conteúdo da mensagem e forneceremos valores padrão para o restante dos dados:

 saveMessage (conteúdo);

Implementando uma função polimórfica

OK, com nossa API definida, podemos construir a implementação de nossa função polimórfica.

//Utilizaremos um array para um banco de dados simples na memória.
banco de dados const=[]; function saveMessage (... args) { //Depois de colocar nossa entrada em um formato unificado, usaremos esta função para //armazene-o em nosso banco de dados e calcule um identificador que representa o //dados. função salvar (registro) { database.push (registro); deixe resultado=''; para (seja i=0; i <5_000; i +=1) { resultado +=registro.autor + registro.contentes; } return result.length; } //Se o desenvolvedor nos passou todos os dados individualmente, vamos empacotar //transforma-o em um objeto e o armazena no banco de dados. if (args.length===3) { const [autor, conteúdo, carimbo de data/hora]=args; retornar salvar ({autor, conteúdo, carimbo de data/hora}); } //Ou, se o desenvolvedor forneceu uma string de mensagem e um objeto de opções, //descobriremos em qual ordem eles vieram e, em seguida, salvaremos de forma adequada. if (args.length===2) { if (typeof args [0]==='string') { const [conteúdo, opções]=args; registro const={autor: opções.autor, conteúdo, carimbo de data/hora: opções. carimbo de tempo}; retornar salvar (registro); } outro { const [opções, conteúdo]=args; registro const={autor: options.author, contents, timestamp: options.timestamp}; retornar salvar (registro); } } //Caso contrário, obtemos uma mensagem de string ou um conjunto completo de //opções. if (args.length===1) { const [arg]=args; if (typeof arg==='string') { //Se o único argumento for a mensagem de string, salve-o no banco de dados //com alguns valores padrão para autor e carimbo de data/hora. const record={ autor:'Anônimo', conteúdo: arg, carimbo de data/hora: nova data (), }; retornar salvar (registro); } outro { //Caso contrário, apenas salve o objeto de opções no banco de dados como está. retornar salvar (arg); } }
}

Ok, agora vamos escrever um código que armazena muitas mensagens usando nossa função-aproveitando sua API polimórfica-e medir seu desempenho.

 const {performance}=require ('perf_hooks'); const start=performance.now ();
para (deixe i=0; i <5_000; i ++) { saveMessage ( 'Homem Morcego', 'Porque caímos? Assim, podemos aprender a nos levantar novamente.', Nova data(), ); saveMessage ( 'A vida não nos dá propósito. Damos propósito à vida.', { autor:'The Flash', carimbo de data/hora: nova data (), }, ); saveMessage ( 'Não importa o quão ruim as coisas fiquem, algo bom está lá fora, no horizonte.', {}, ); saveMessage ( { autor:'Tio Ben', carimbo de data/hora: nova data (), }, 'Com grandes poderes vem grandes responsabilidades.', ); saveMessage ({ autor:'Sra. Maravilha', conteúdo:'Quando você decidir não ter medo, você pode encontrar amigos em lugares super inesperados.', carimbo de data/hora: nova data (), }); saveMessage ( "Mais vale tarde do que nunca, mas nunca é melhor tarde." );
}
console.log (`Registros $ {database.length} inseridos no banco de dados`);
console.log (`Duração: $ {(performance.now ()-início).toFixed (2)} milissegundos`);

Agora vamos implementar nossa função novamente, mas com uma API monomórfica mais simples.

Criação de uma função monomórfica em JavaScript

Em troca de uma API mais restritiva, podemos reduzir a complexidade da nossa função e torná-la monomórfica, o que significa que os argumentos da função são sempre do mesmo tipo e na mesma ordem.

Embora não seja tão flexível, podemos manter um pouco da ergonomia da implementação anterior por utilizando argumentos padrão . Nossa nova função será semelhante a esta:

//Voltaremos a utilizar um array para um banco de dados simples na memória.
banco de dados const=[]; //Em vez de uma lista genérica de argumentos, pegaremos o conteúdo da mensagem e
//opcionalmente, o autor e o carimbo de data/hora.
function saveMessage (contents, author='Anonymous', timestamp=new Date ()) { //Primeiro, salvaremos nosso registro em nosso array de banco de dados. database.push ({autor, conteúdo, carimbo de data/hora}); //Como antes, calcularemos e retornaremos um identificador que representa o //dados, mas vamos embutir o conteúdo da função, pois não há necessidade //para reutilizá-lo. deixe resultado=''; para (seja i=0; i <5_000; i +=1) { resultado +=autor + conteúdo; } return result.length;
}

Atualizaremos o código de medição de desempenho de nosso exemplo anterior para usar nossa nova API unificada.

 const {performance}=require ('perf_hooks'); const start=performance.now ();
para (deixe i=0; i <5_000; i ++) { saveMessage ( 'Porque caímos? Assim, podemos aprender a nos levantar novamente.', 'Homem Morcego', Nova data(), ); saveMessage ( 'A vida não nos dá propósito. Damos propósito à vida.', 'O Flash', Nova data(), ); saveMessage ( 'Não importa o quão ruim as coisas fiquem, algo bom está lá fora, no horizonte.', ); saveMessage ( 'Com grandes poderes vem grandes responsabilidades.', 'Tio ben', Nova data(), ); saveMessage ( 'Quando você decide não ter medo, você pode encontrar amigos em lugares super inesperados.', 'Em. Maravilha', Nova data(), ); saveMessage ( "Mais vale tarde do que nunca, mas nunca é melhor tarde." );
}
console.log (`Registros $ {database.length} inseridos no banco de dados`);
console.log (`Duração: $ {(performance.now ()-início).toFixed (2)} milissegundos`);

Comparando resultados monomórficos e polimórficos

OK, agora vamos executar nossos programas e comparar os resultados.

 $ node polymorphic.js
Inseriu 30000 registros no banco de dados.
Duração: 6565,41 milissegundos $ node monomorphic.js
Inseriu 30000 registros no banco de dados.
Duração: 2955,01 milissegundos

A versão monomórfica de nossa função é cerca de duas vezes mais rápida que a versão polimórfica, pois há menos código para executar na versão monomórfica. Mas, como os tipos e formatos dos argumentos na versão polimórfica variam amplamente, o V8 tem mais dificuldade para fazer otimizações em nosso código.

Em termos simples, quando V8 pode identificar (a) que chamamos uma função com frequência, e (b) que a função é chamada com os mesmos tipos de argumentos, V8 pode criar"atalhos"para coisas como pesquisas de propriedades de objetos, aritmética, operações de string e muito mais.

Para uma análise mais aprofundada de como esses “atalhos” funcionam, recomendo este artigo: O que há com monomorfismo? por Vyacheslav Egorov.

Prós e contras das funções polimórficas e monomórficas

Antes de otimizar todo o seu código para ser monomórfico, existem alguns pontos importantes a serem considerados primeiro.

As chamadas de função polimórfica provavelmente não são o seu gargalo de desempenho. Existem muitos outros tipos de operações que contribuem muito mais comumente para os problemas de desempenho, como chamadas de rede latentes, movendo grandes quantidades de dados em memória, i/o de disco, consultas complexas de banco de dados, para citar apenas alguns.

Você só terá problemas de desempenho com funções polimórficas se essas funções forem muito, muito"quentes"(executadas com frequência). Apenas aplicativos altamente especializados, semelhantes aos nossos exemplos inventados acima, se beneficiarão com otimização neste nível. Se você tiver uma função polimórfica que é executada apenas algumas vezes, não haverá benefício em reescrevê-la para ser monomórfica.

Você terá mais sorte atualizando seu código para ser eficiente, em vez de tentar otimizar para o mecanismo JavaScript. Na maioria dos casos, aplicar bons princípios de design de software e prestar atenção à complexidade de seu código levá-lo mais longe do que se concentrar no tempo de execução subjacente. Além disso, o V8 e outros motores estão constantemente ficando mais rápidos, então algumas otimizações de desempenho que funcionam hoje podem se tornar irrelevantes em uma versão futura do motor.

Conclusão

APIs polimórficas podem ser convenientes de usar devido à sua flexibilidade. Em certas situações, sua execução pode ser mais cara, pois os mecanismos JavaScript não podem otimizá-los de forma tão agressiva quanto as funções monomórficas mais simples.

Em muitos casos, no entanto, a diferença será insignificante. Os padrões de API devem se basear em outros fatores, como legibilidade, consistência e capacidade de manutenção, porque os problemas de desempenho têm maior probabilidade de surgir em outras áreas. Boa programação!

A postagem Como funções polimórficas de JavaScript afetam o desempenho apareceu primeiro em LogRocket Blog .

Source link