Lançamento do V8 v7.4
A cada seis semanas, criamos um novo branch do V8 como parte do nosso processo de lançamento. Cada versão é ramificada a partir da ramificação principal do Git do V8 imediatamente antes de um marco Beta do Chrome. Hoje, temos o prazer de anunciar nosso mais novo branch, V8 versão 7.4, que está em beta até seu lançamento em coordenação com o Chrome 74 Stable em algumas semanas. O V8 v7.4 está repleto de recursos voltados para desenvolvedores. Este post oferece uma prévia de alguns dos destaques em antecipação ao lançamento.
V8 sem JIT
Agora, o V8 oferece suporte à execução de JavaScript sem alocar memória executável em tempo de execução. Informações detalhadas sobre esse recurso podem ser encontradas no post dedicado.
Threads/Átomos WebAssembly lançados
Agora, os threads/átomos WebAssembly estão habilitados em sistemas operacionais que não sejam Android. Isso conclui o teste de origem/visualização que habilitamos no V8 v7.0. Um artigo nos Fundamentos da Web explica como usar átomos WebAssembly com Emscripten.
Isso desbloqueia o uso de múltiplos núcleos no computador do usuário via WebAssembly, possibilitando novos casos de uso que exigem muita computação na web.
Desempenho
Chamadas mais rápidas com incompatibilidade de argumentos
Em JavaScript, é perfeitamente válido chamar funções com poucos ou muitos parâmetros (ou seja, passar menos ou mais do que os parâmetros formais declarados). O primeiro caso é chamado de subaplicação, o segundo é chamado de sobreaplicação. No caso de subaplicação, os parâmetros formais restantes recebem o valor undefined
, enquanto no caso de sobreaplicação, os parâmetros supérfluos são ignorados.
No entanto, as funções JavaScript ainda podem acessar os parâmetros reais por meio do objeto arguments
, usando parâmetros rest, ou mesmo usando a propriedade não padrão Function.prototype.arguments
em funções no modo não estrito. Como resultado, os motores do JavaScript devem fornecer uma maneira de acessar os parâmetros reais. No V8, isso é feito por meio de uma técnica chamada adaptação de argumentos, que fornece os parâmetros reais em casos de subaplicação ou sobreaplicação. Infelizmente, a adaptação de argumentos tem um custo de desempenho e é comumente necessária nas estruturas modernas de front-end e middleware (ou seja, muitas APIs com parâmetros opcionais ou listas de argumentos variáveis).
Há cenários em que o motor sabe que a adaptação de argumentos não é necessária, visto que os parâmetros reais não podem ser observados, nomeadamente quando a função chamada está no modo estrito e não usa nem arguments
nem parâmetros rest. Nesses casos, o V8 agora ignora completamente a adaptação de argumentos, reduzindo o overhead de chamadas em até 60%.
O gráfico mostra que não há mais overhead, mesmo no caso de uma incompatibilidade de argumentos (supondo que a função chamada não consiga observar os argumentos reais). Para mais detalhes, veja o documento de design.
Desempenho aprimorado de acessores nativos
A equipe do Angular descobriu que chamar acessores nativos (ou seja, acessores de propriedade DOM) diretamente por meio de suas respectivas funções get
era significativamente mais lento no Chrome do que o acesso de propriedade monomórfico ou até megamórfico. Isso se deve ao caminho lento no V8 para chamadas em acessores DOM via Function#call()
, em vez do caminho rápido que já existia para acessos de propriedades.
Conseguimos melhorar o desempenho ao chamar acessores nativos, tornando-o significativamente mais rápido do que o acesso a propriedades megamórficas. Para mais informações, veja V8 issue #8820.
Desempenho do analisador
No Chrome, scripts grandes o suficiente são analisados em "streaming" em threads de trabalho enquanto estão sendo baixados. Nesta versão, identificamos e corrigimos um problema de desempenho com a decodificação UTF-8 personalizada usada pelo fluxo de origem, resultando em uma análise em streaming 8% mais rápida, em média.
Encontramos um problema adicional no preparador do V8, que geralmente é executado em um thread de trabalho: nomes de propriedades estavam sendo desduplicados desnecessariamente. Remover essa desduplicação melhorou o analisador em streaming em mais 10,5%. Isso também melhora o tempo de análise no thread principal de scripts que não são transmitidos, como pequenos scripts e scripts inline.
Memória
Descarte de bytecode
O bytecode compilado a partir da fonte JavaScript ocupa uma parte significativa do espaço do heap do V8, tipicamente cerca de 15%, incluindo metadados relacionados. Existem muitas funções que são executadas apenas durante a inicialização ou raramente usadas após terem sido compiladas.
Para reduzir o overhead de memória do V8, implementamos suporte para descartar bytecodes compilados de funções durante a coleta de lixo, caso não tenham sido executados recentemente. Para habilitar isso, rastreamos a idade do bytecode de uma função, incrementando a idade durante coletas de lixo, e redefinindo-a para zero quando a função é executada. Qualquer bytecode que cruzar um limiar de idade será elegível para ser coletado na próxima coleta de lixo, e a função será reconfigurada para recompilar seu bytecode de forma preguiçosa caso seja executada novamente no futuro.
Nossos experimentos com descarte de bytecode mostram que ele proporciona uma economia significativa de memória para os usuários do Chrome, reduzindo a quantidade de memória no heap do V8 em cerca de 5–15%, sem prejudicar o desempenho ou aumentar significativamente o tempo de CPU gasto na compilação de código JavaScript.
Eliminação de blocos básicos de bytecode mortos
O compilador de bytecode Ignition tenta evitar gerar código que sabe ser morto, por exemplo, código após uma instrução return
ou break
:
return;
deadCall(); // ignorado
No entanto, anteriormente isso era feito oportunisticamente para instruções que terminam na lista de instruções, então não levava em conta outras otimizações, como atalhos de condições que são conhecidas por serem verdadeiras:
if (2.2) return;
deadCall(); // não ignorado
Tentamos resolver isso no V8 v7.3, mas ainda em nível de instrução, o que não funcionaria quando o fluxo de controle se tornava mais complexo, por exemplo:
do {
if (2.2) return;
break;
} while (true);
deadCall(); // não ignorado
O deadCall()
acima estaria no início de um novo bloco básico, que em nível de instrução seria acessível como um alvo para instruções break
no laço.
No V8 v7.4, permitimos que blocos básicos inteiros se tornem mortos, se nenhum bytecode de Jump
(o principal primitivo de controle de fluxo do Ignition) se referir a eles. No exemplo acima, o break
não é emitido, o que significa que o laço não tem instruções break
. Assim, o bloco básico que começa com deadCall()
não tem saltos que o referenciem e, portanto, também é considerado morto. Embora não esperamos um impacto significativo no código do usuário, isso é particularmente útil para simplificar várias desestruturações, como geradores, for-of
e try-catch
, e, em particular, remove uma classe de bugs onde blocos básicos poderiam “ressuscitar” instruções complexas no meio de sua implementação.
Recursos da linguagem JavaScript
Campos privados de classes
O V8 v7.2 adicionou suporte para a sintaxe de campos públicos de classes. Os campos de classes simplificam a sintaxe de classes ao evitar a necessidade de funções construtoras apenas para definir propriedades de instâncias. A partir do V8 v7.4, você pode marcar um campo como privado ao adicioná-lo com um prefixo #
.
class IncreasingCounter {
#count = 0;
get value() {
console.log('Obtendo o valor atual!');
return this.#count;
}
increment() {
this.#count++;
}
}
Ao contrário dos campos públicos, os campos privados não são acessíveis fora do corpo da classe:
const counter = new IncreasingCounter();
counter.#count;
// → SyntaxError
counter.#count = 42;
// → SyntaxError
Para mais informações, leia nosso artigo explicativo sobre campos públicos e privados de classes.
Intl.Locale
Os aplicativos JavaScript geralmente usam strings como 'en-US'
ou 'de-CH'
para identificar locais. Intl.Locale
oferece um mecanismo mais poderoso para lidar com locais, permitindo extrair facilmente preferências específicas do local, como o idioma, o calendário, o sistema numérico, o ciclo de horas e assim por diante.
const locale = new Intl.Locale('es-419-u-hc-h12', {
calendar: 'gregory'
});
locale.language;
// → 'es'
locale.calendar;
// → 'gregory'
locale.hourCycle;
// → 'h12'
locale.region;
// → '419'
locale.toString();
// → 'es-419-u-ca-gregory-hc-h12'
Gramática do Hashbang
Os programas JavaScript agora podem começar com #!
, um chamado hashbang. O resto da linha que segue o hashbang é tratado como um comentário de linha única. Isso corresponde ao uso de fato em hosts de linha de comando JavaScript, como Node.js. O seguinte agora é um programa JavaScript sintaticamente válido:
#!/usr/bin/env node
console.log(42);
API do V8
Por favor, use git log branch-heads/7.3..branch-heads/7.4 include/v8.h
para obter uma lista das mudanças na API.
Os desenvolvedores com um checkout ativo do V8 podem usar git checkout -b 7.4 -t branch-heads/7.4
para experimentar os novos recursos no V8 v7.4. Alternativamente, você pode assinar o canal Beta do Chrome e experimentar os novos recursos em breve.