Segredos para um JavaScript performático

William Grasel

Quem se importa com otimização de código?

Você deveria!

Não é apenas sobre ser mais rápido...

É sobre viabilizar coisas impossíveis antes!

Você não precisa ser

o chato das

micro-otimizações

Nem fazer mega otimizações precoces!

Apenas entender o ambiente que trabalha!

Para pelo menos não cometer erros facilmente evitáveis!

Pipeline de Compilação do JavaScript!

Ignition / Full Codegen

  • Tem como principal objetivo executar seu código pela primeira vez o mais rápido possível
  • Colhe informações sobre a execução do seu código para futuras otimizações
  • Começa a otimizar a execução do seu código, mas ainda de maneira conservadora
  • Já consegue otimizar a execução do código em algumas centenas de vezes, em tempo de execução.
  • Mudanças constantes de Classes Ocultas impedem que as otimizações sejam criadas e utilizadas

TurboFan / Crankshaft

  • Funções "quentes" mais utilizadas durante a execução do código são recompiladas
  • Utiliza todas as informações anteriores para gerar otimizações agressivas
  • Utiliza especulações sem checar os tipos de dados e Classes Ocultas antes da execução
  • Pode sofrer desotimização e voltar para a versão do compilador anterior

Classes Ocultas

  • Uma das principais formas como o V8 colhe informações sobre o tipo de dados durante a execução do seu código
  • Objetos com a mesma classe oculta tira proveito das mesmas otimizações de código

Exemplo:


  function Point(x, y) {  
    this.X = x;
    this.Y = y;
  }

  function Point(x, y) {  
    this.X = x;
    this.Y = y;
  }

  var p1 = new Point(11, 22);

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y;
  }

  var p1 = new Point(11, 22);

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22);

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B
  var p2 = new Point(33, 44);

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B
  var p2 = new Point(33, 44); // usando classe oculta B

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B
  var p2 = new Point(33, 44); // usando classe oculta B

  p1.Z = 55;

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B
  var p2 = new Point(33, 44); // usando classe oculta B

  p1.Z = 55; // classe oculta C criada  

  function Point(x, y) {  
    this.X = x; // classe oculta A criada
    this.Y = y; // classe oculta B criada
  }

  var p1 = new Point(11, 22); // usando classe oculta B
  var p2 = new Point(33, 44); // usando classe oculta B

  p1.Z = 55; // classe oculta C criada
  // p1 e p2 agora usam classes ocultas diferentes!
Point A
X
Point B
X
Y
Point C
X
Y
Z

Atenção!

  • Inicialize todos os seus atributos em uma função construtora.
  • Inicialize todos seus atributos sempre na mesma ordem.
  • Não delete atributos de seus objetos.

Construa objetos previsíveis

Arrays

  • Acesso Rápido: armazenamento linear
  • Acesso em Dicionário: armazenamento em hash table

Servidos em dois sabores:

Atenção!

  • Dê preferência a arrays de acesso rápido
  • Use índices numéricos contínuos a partir do 0
  • Não pré-aloque arrays muito grandes com mais de 64 mil itens
  • Não delete itens da array diretamente, use Array.splice ou gere um novo array com Array.filter
  • Não acesse índices inexistentes ou deletados do array

Classes Ocultas para Arrays

1 - Apenas Inteiros

2 - Todos os tipos de números

3 - Todos os demais tipos

Exemplo


  let a = new Array();

  let a = new Array();
  // Nada alocado, assumindo Classe Oculta de Inteiros

  let a = new Array();
  // Nada alocado, assumindo Classe Oculta de Inteiros

  a[0] = 77;   // Primeira alocação

  let a = new Array();
  // Nada alocado, assumindo Classe Oculta de Inteiros

  a[0] = 77;   // Primeira alocação
  a[1] = 88;

  let a = new Array();
  // Nada alocado, assumindo Classe Oculta de Inteiros

  a[0] = 77;   // Primeira alocação
  a[1] = 88;
  a[2] = 0.5;  // Mudança de Classse Oculta... Realoca

  let a = new Array();
  // Nada alocado, assumindo Classe Oculta de Inteiros

  a[0] = 77;   // Primeira alocação
  a[1] = 88;
  a[2] = 0.5;  // Mudança de Classse Oculta... Realoca
  a[3] = true; // Mudança de Classse Oculta... Realoca

Padronize as chamadas de funções

  • Funções chamadas da mesma forma, com o mesmo tipo de dado, são rápida e facilmente otimizadas!
  • Funções chamadas com tipos de dados muito variáveis dificilmente serão otimizadas ou podem ser desotimizadas

JavaScript é uma linguagem dinâmica!

Mas os tipos ainda estão lá, e isso pode estar te custando caro!

Grandes poderes vem com grandes responsabilidades!

TypeScript pode te ajudar nessa jornada!

😉

Referências

Perguntas?

Obrigado! =)

Segredos para um JavaScript performático

By William Grasel

Segredos para um JavaScript performático

Entendendo como o V8 e outras engines de JavaScript executam e otimizam nosso código por baixo dos panos, podemos evitar alguns erros básicos que podem nos custar muito caro, e assim escrever um código naturalmente mais performático e evitando dores de cabeça no futuro.

  • 2,768