var toSizeString = (function() {
var KB = 1024.0,
MB = 1024 * KB,
GB = 1024 * MB;
return function(size) {
var gbSize = size / GB,
gbMod = size % GB,
mbSize = gbMod / MB,
mbMod = gbMod % MB,
kbSize = mbMod / KB;
if (Math.floor(gbSize)) {
return gbSize.toFixed(1) + 'GB';
} else if (Math.floor(mbSize)) {
return mbSize.toFixed(1) + 'MB';
} else if (Math.floor(kbSize)) {
return kbSize.toFixed(1) + 'KB';
} else {
return size + 'B';
}
};
})();
E a função mais rápida: (observe que sempre deve calcular as mesmas variáveis kb / mb / gb repetidas vezes). Onde ele ganha desempenho?
function toSizeString (size) {
var KB = 1024.0,
MB = 1024 * KB,
GB = 1024 * MB;
var gbSize = size / GB,
gbMod = size % GB,
mbSize = gbMod / MB,
mbMod = gbMod % MB,
kbSize = mbMod / KB;
if (Math.floor(gbSize)) {
return gbSize.toFixed(1) + 'GB';
} else if (Math.floor(mbSize)) {
return mbSize.toFixed(1) + 'MB';
} else if (Math.floor(kbSize)) {
return kbSize.toFixed(1) + 'KB';
} else {
return size + 'B';
}
};
javascript
performance
Para o meu
fonte
fonte
Respostas:
Os modernos mecanismos JavaScript fazem compilação just-in-time. Você não pode fazer nenhuma suposição sobre o que "deve criar repetidamente". Esse tipo de cálculo é relativamente fácil de otimizar, em ambos os casos.
Por outro lado, fechar sobre variáveis constantes não é um caso típico para o qual você visaria a compilação JIT. Você normalmente cria um fechamento quando deseja poder alterar essas variáveis em diferentes invocações. Você também está criando uma desreferência adicional de ponteiro para acessar essas variáveis, como a diferença entre acessar uma variável de membro e um int local no OOP.
Esse tipo de situação é o motivo pelo qual as pessoas jogam fora a linha da "otimização prematura". As otimizações fáceis já são feitas pelo compilador.
fonte
Variáveis são baratas. Contextos de execução e cadeias de escopo são caros.
Existem várias respostas que se resumem basicamente a "porque fechamentos", e essas são essencialmente verdadeiras, mas o problema não está especificamente no fechamento, é o fato de você ter uma função que faz referência a variáveis em um escopo diferente. Você teria o mesmo problema se essas fossem variáveis globais no
window
objeto, em oposição às variáveis locais dentro do IIFE. Experimente e veja.Então, na sua primeira função, quando o mecanismo vê esta declaração:
É necessário seguir os seguintes passos:
size
no escopo atual. (Encontrei.)GB
no escopo atual. (Não encontrado.)GB
no escopo pai. (Encontrei.)gbSize
.O passo 3 é consideravelmente mais caro do que apenas alocar uma variável. Além disso, você faz isso cinco vezes , incluindo duas vezes para ambos
GB
eMB
. Eu suspeito que, se você aliasse esses itens no início da função (por exemplovar gb = GB
) e referenciasse o alias, isso produziria uma pequena aceleração, embora também seja possível que alguns mecanismos JS já executem essa otimização. E, é claro, a maneira mais eficaz de acelerar a execução é simplesmente não atravessar a cadeia de escopo.Lembre-se de que o JavaScript não é como uma linguagem compilada estaticamente, em que o compilador resolve esses endereços variáveis no momento da compilação. O mecanismo JS deve resolvê-los pelo nome , e essas pesquisas acontecem em tempo de execução, sempre. Então você quer evitá-los quando possível.
A atribuição de variáveis é extremamente barata em JavaScript. Pode ser realmente a operação mais barata, embora eu não tenha nada para fazer backup dessa declaração. No entanto, é seguro dizer que quase nunca é uma boa idéia tentar evitar a criação de variáveis; quase todas as otimizações que você tenta fazer nessa área acabam piorando as coisas, em termos de desempenho.
fonte
var a, b, c
que possamos acessarb
comoscope[1]
. Todos os escopos são numerados e, se esse escopo estiver aninhado com cinco escopos,b
será totalmente endereçado peloenv[5][1]
qual é conhecido durante a análise. No código nativo, os escopos correspondem aos segmentos da pilha. Os fechamentos são mais complicados, pois precisam fazer backup e substituir oenv
*(scope->outer + variable_offset)
para um acesso; cada nível de escopo de função extra custa uma desreferência adicional de ponteiro. Parece que nós dois estávamos certo :)Um exemplo envolve um fechamento, o outro não. A implementação de fechamentos é meio complicada, pois as variáveis fechadas não funcionam como variáveis normais. Isso é mais óbvio em uma linguagem de baixo nível como C, mas usarei JavaScript para ilustrar isso.
Um fechamento não consiste apenas de uma função, mas também de todas as variáveis que ele encerrou. Quando queremos chamar essa função, também precisamos fornecer todas as variáveis de fechamento. Podemos modelar um fechamento por uma função que recebe um objeto como primeiro argumento que representa essas variáveis fechadas sobre:
Observe a convenção de chamada estranha que
closure.apply(closure, ...realArgs)
isso exigeO suporte a objetos embutidos do JavaScript permite omitir o
vars
argumento explícito e, emthis
vez disso , vamos usar :Esses exemplos são equivalentes a esse código, na verdade, usando closures:
Neste último exemplo, o objeto é usado apenas para agrupar as duas funções retornadas; a
this
ligação é irrelevante. Todos os detalhes de tornar possíveis os fechamentos - passando dados ocultos para a função real, alterando todos os acessos às variáveis de fechamento para pesquisas nesses dados ocultos - são resolvidos pelo idioma.Mas chamar encerramentos envolve a sobrecarga de passar esses dados extras, e executar um encerramento envolve a sobrecarga de pesquisas nesses dados extras - agravadas pela má localização do cache e, geralmente, por uma desreferência de ponteiro quando comparadas às variáveis comuns -, portanto, não é surpreendente que uma solução que não depende de fechamentos tem melhor desempenho. Especialmente porque tudo o que seu fechamento lhe permite fazer são algumas operações aritméticas extremamente baratas, que podem até ser dobradas constantemente durante a análise.
fonte