- No motor V8, a performance da função JSON.stringify foi aumentada em mais de 2x, melhorando a velocidade de serialização de dados
- Foi introduzido um caminho de otimização para objetos sem efeitos colaterais, omitindo várias verificações defensivas e trazendo grande ganho de velocidade para objetos de dados comuns
- No processamento de strings, foram aplicados métodos avançados do ponto de vista de hardware e memória, como distinção entre 1 byte/2 bytes, uso de SIMD e mudança na estrutura de buffers temporários
- No processo de conversão de números, o algoritmo Grisu3 foi substituído por Dragonbox, abrindo caminho para conversões mais rápidas em chamadas de
Number.toString() como um todo
- Em alguns argumentos e formatos, o processamento volta ao caminho normal de serialização, mas na maioria dos cenários de desenvolvimento web o efeito da otimização é aproveitado automaticamente
Visão geral
JSON.stringify é a função central para converter dados em string no JavaScript
- Melhorar a performance dessa função também impacta positivamente tarefas muito importantes na web, como requisições de rede e salvamento no localStorage
- Com a engenharia mais recente do V8, a velocidade desse recurso foi mais que dobrada, e as principais estratégias de otimização são apresentadas em detalhe
Fast Path sem efeitos colaterais
- O núcleo da otimização é aplicar um caminho rápido de serialização que só pode ser usado em situações sem efeitos colaterais (side effects)
- Nessas situações, em vez de recursão, os objetos são percorridos com uma estrutura iterativa, o que elimina a necessidade de verificação de stack overflow e permite tentar serializar objetos mais profundos
- Quando o objeto de dados é simples, o V8 usa esse Fast Path no lugar da lógica geral mais lenta, omitindo muitas verificações e aumentando a velocidade
Tratamento de diferentes representações de strings
- O V8 armazena strings de forma diferente conforme sejam caracteres de 1 byte/2 bytes (ASCII/não ASCII); se houver ao menos um caractere não ASCII, tudo passa a ser tratado em 2 bytes
- Para melhorar a performance da serialização, são compiladas versões separadas do algoritmo para cada tipo de string
- Como durante o processamento é preciso verificar o tipo da instância da string, quando uma string de 2 bytes é detectada, o serializador apropriado de 2 bytes assume o estado
- Com isso, praticamente não há sobrecarga na troca de caminho por codificação de string
- O resultado final é produzido criando buffers separados de 1 byte e 2 bytes, que são simplesmente combinados no final
Otimização da serialização de strings com SIMD
- Strings JavaScript podem conter caracteres que precisam ser escapados durante a serialização em JSON
- Strings longas são verificadas vários bytes de uma vez com instruções de hardware SIMD (como ARM64 Neon)
- Strings curtas usam a abordagem SWAR, processando vários caracteres simultaneamente com operações de bits em registradores de uso geral
- Em qualquer dos métodos, na maioria dos casos é possível copiar a string inteira rapidamente sem transformações relevantes
Adição da Express Lane (caminho ultrarrápido)
- Mesmo dentro do Fast Path, foi criada uma Express Lane para permitir serializar apenas copiando chaves, sem trabalho repetitivo como verificação de propriedades
- Usando a flag de hidden class do objeto, quando as chaves não têm Symbol, todas são enumerable e já foram serializadas sem necessidade de escape, o objeto é marcado como
fast-json-iterable
- Ao serializar outros objetos com a mesma hidden class, a cópia das chaves é feita imediatamente, sem verificações adicionais
- Essa técnica também é aplicada em
JSON.parse para comparação rápida de chaves
Algoritmo double-to-string mais rápido
- O processo de converter números em string também é frequente e complexo
- O algoritmo Grisu3 existente foi substituído por Dragonbox, gerando ganho de performance em chamadas de
Number.prototype.toString() como um todo
Otimização da estrutura de buffers temporários
- Ao montar strings, antes era usado um buffer contínuo único, o que causava a sobrecarga de copiar tudo novamente sempre que o espaço acabava
- O novo método usa uma estrutura de buffer segmentado, conectando vários buffers menores conforme necessário
- Assim, quando falta espaço, basta alocar um novo buffer, sem necessidade de copiar tudo
Limitações
- O Fast Path funciona apenas para serialização de dados simples
- Se as condições abaixo não forem atendidas, o caminho normal é usado
- Não é possível usar os argumentos replacer ou space (sem Pretty-Print ou transformação)
- Deve ser um objeto simples, sem método customizado toJSON
- Se houver propriedades baseadas em índice, o processamento muda para o caminho lento
- Strings especiais como ConsString não são tratadas
- Para a maioria dos usos comuns, como serialização de dados, geração de respostas de API e cache de configurações, o efeito da otimização é aplicado automaticamente
Conclusão
- Ao reorganizar a abordagem em toda a função
JSON.stringify, do design básico ao tratamento de memória e caracteres, foi alcançado um ganho de mais de 2x de velocidade no benchmark JetStream2
- Essas melhorias já podem ser percebidas a partir do V8 versão 13.8 (Chrome 138) ou superior
Ainda não há comentários.