JSON이 정말 크게 개선되었습니다 (정말입니다)

채널 아이콘
Theo - t3․gg 구독자 439,000명

요약

이 영상에서는 Chrome V8 팀이 JSON.stringify 및 JSON.parse를 최대 3배가량 더 빠르게 만든 기술적 최적화 과정을 다룹니다. 사이드 이펙트 없는 fast path, 1바이트/2바이트 문자열 전용 시리얼라이저, SIMD·SWAR 기반 이스케이프 검사, Dragonbox 알고리즘, segmented buffer, hidden class 활용 등 다양한 기법이 등장합니다. 이를 통해 네트워크 요청 직렬화부터 로컬 저장소 저장까지 일상적인 웹 애플리케이션 경험이 더욱 빨라진다는 핵심 메시지를 전합니다.

주요 키워드

JSON.stringify JSON.parse Fast Path V8 엔진 SIMD SWAR Hidden Class Dragonbox Segmented Buffer 객체 직렬화

하이라이트

  • 🔑 Chrome V8 팀의 최적화로 JSON.stringify 성능이 2배에서 최대 3배까지 향상되었습니다.
  • 🚀 사이드 이펙트 없이 순수 데이터만 직렬화하는 fast path를 구현해 불필요한 검사 비용을 제거했습니다.
  • ⚡️ 1바이트 문자열과 2바이트 문자열 전용 시리얼라이저를 각각 컴파일해 분기 없이 메모리 이용 효율을 높였습니다.
  • 💥 SIMD(ARM NEON)·SWAR(bitwise) 기술을 활용해 큰 문자열과 작은 문자열 모두에서 이스케이프 문자 검사를 획기적으로 가속화했습니다.
  • 📌 segmented buffer 도입으로 버퍼 재할당 및 복사를 완전히 제거해 대용량 JSON 객체 직렬화 속도를 개선했습니다.
  • 🌟 hidden class 기반 fast JSON iterable 플래그를 활용해 같은 구조 객체 반복 직렬화를 최적화했습니다.
  • 🧩 Dragonbox 알고리즘을 도입해 숫자→문자열 변환을 더욱 빠르고 정확하게 처리합니다.
  • 💡 JSON.parse를 활용해 복잡한 객체를 리터럴 대신 문자열로 정의하면 초기 로딩 성능을 최적화할 수 있습니다.
  • ⚠️ replacer, space 옵션, 커스텀 toJSON 메서드, 배열 인덱스 프로퍼티 등은 fast path를 우회하므로 주의해야 합니다.

용어 설명

Fast Path

사이드 이펙트가 없는 단순 객체를 위한 특화 직렬화 경로로, 일반 경로 대비 검사 비용을 줄여 속도를 높임

V8 엔진

Chrome과 Node.js에서 사용하는 구글의 자바스크립트 엔진

SIMD

Single Instruction, Multiple Data: 단일 명령어로 여러 데이터를 동시에 처리하는 병렬 연산 기술

SWAR

SIMD Within A Register: 일반 레지스터 내에서 비트 연산만으로 벡터화 연산을 수행하는 기법

Hidden Class

객체의 프로퍼티 구조(shape)를 표현하는 내부 메타데이터로, 동일한 맵을 공유하면 메모리 위치를 빠르게 조회 가능

Dragonbox

숫자를 문자열로 변환할 때 최단 길이를 보장하는 알고리즘으로, V8에서는 기존 Grisu3에서 교체되어 성능을 개선함

Segmented Buffer

출력 버퍼를 작은 조각으로 나눠 필요할 때만 할당·연결해 대규모 복사 비용을 제거하는 메모리 관리 기법

JSON.stringify

JavaScript 객체를 JSON 문자열로 직렬화하는 표준 함수

JSON.parse

JSON 문자열을 JavaScript 객체로 역직렬화하는 표준 함수

[00:00:00] 서론: JavaScript vs JSON 속도

JavaScript 언어 자체는 느리지만, JSON.parse/stringify 성능은 놀라울 정도로 빠르다는 점을 짚습니다. 간단한 직렬화가 왜 JavaScript 객체 노테이션에 최적화될 수밖에 없는지도 설명합니다.

JavaScript는 빠르지 않은 언어지만, JSON의 parse와 stringify 성능은 놀라울 정도로 뛰어나며, Chrome 팀의 최신 업데이트로 거의 3배까지 빨라졌다는 흥미로운 사실을 소개합니다.
[00:00:30] Chrome V8의 대대적 최적화

최근 V8 팀이 JSON.stringify를 2~3배 빠르게 만든 개선 작업을 소개합니다. 구현 세부 사항과 V8 성능 특이점을 다루는 재미있는 에피소드를 예고합니다.

JSON stringify의 성능 향상에 대한 깊이 있는 분석과 함께, 객체를 직접 정의하는 것보다 JSON으로 정의한 후 파싱하는 것이 VM에서 더 빠를 수 있다는 흥미로운 개발 팁을 공유합니다.
오늘의 스폰서인 Augment Code를 소개하며, AI 기반 애플리케이션 개발에서 대규모 코드베이스 관리의 어려움과 이를 해결하는 Augment의 솔루션에 대해 설명합니다.
[00:01:19] 스폰서: Augment Code 소개

대규모 코드베이스에서도 컨텍스트를 관리해주는 AI 도구 Augment Code를 소개합니다. 배경 에이전트를 통한 클라우드 PR 기능과 실제 사용자 경험을 공유합니다.

Augment Code의 백그라운드 에이전트 기능과 전체 코드베이스 컨텍스트 관리 능력을 강조하며, SWEBench에서의 최고 성과와 Uber, Lemonade, Vercel 등 대기업들의 채택 사례를 언급합니다.
Augment 개발 도구에 대한 사용 경험과 추천을 설명하며, 거대한 코드베이스 탐색과 오픈소스 프로젝트 이해에 매우 유용하다고 강조합니다.
JSON.stringify의 성능 개선에 대한 흥미와 기대를 표현하며, 이 함수의 중요성과 웹 개발에서의 핵심적인 역할을 설명합니다.
[00:02:58] JSON.stringify 핵심 역할

JSON.stringify는 네트워크 요청 데이터 직렬화부터 로컬 스토리지 저장까지 웹 전반에 걸쳐 사용되는 핵심 함수입니다. 성능 개선이 페이지 반응성과 앱 경험에 미치는 영향을 다룹니다.

JSON.stringify 성능이 웹 전반의 작업에 미치는 영향을 설명하고, V8에서 두 배 이상의 속도 향상을 달성했다는 중요한 개선 사항을 발표합니다.
JSON을 이용한 객체 복제라는 일반적이지만 문제가 있는 패턴을 예시로 들며, 부작용 없는 고속 처리 경로의 필요성을 강조합니다.
[00:03:57] 사이드 이펙트 없는 Fast Path

직렬화 도중 사용자 정의 코드 실행이나 가비지 컬렉션을 유발하는 내부 동작을 배제해 순수 데이터만 빠르게 처리하는 전용 경로를 설계한 배경과 조건을 설명합니다.

새로운 최적화의 핵심 원리를 설명합니다. 객체 직렬화가 부작용을 일으키지 않는다고 보장할 수 있을 때 훨씬 빠른 특화된 구현을 사용할 수 있다는 개념을 소개합니다.
부작용의 정의와 범위를 상세히 설명하며, 사용자 정의 코드 실행부터 가비지 컬렉션을 유발하는 미묘한 내부 작업까지 포함된다고 설명합니다.
개인적인 경험과 이전 비디오에서의 V8 가비지 컬렉션 관련 경험을 언급하며, 이번 주제의 기술적 깊이에 대한 우려와 기대를 표현합니다.
[00:04:53] 문자열 표현 최적화

V8의 문자열은 1바이트(ASCII) 또는 2바이트(비ASCII)로 구분됩니다. 템플릿화 컴파일로 두 가지 전용 시리얼라이저를 만들어 메모리 사용과 분기 비용을 최소화한 방식을 살펴봅니다.

V8이 JSON 직렬화에서 사이드 이펙트가 없다고 판단하면 고도로 최적화된 경로를 유지할 수 있습니다. 이를 통해 범용 직렬화기에서 필요했던 비싼 검사와 방어적 로직을 우회하여 상당한 속도 향상을 가져올 수 있습니다.
새로운 빠른 경로는 재귀적인 범용 직렬화기와 달리 반복적인 방식을 사용합니다. 이 아키텍처 선택은 스택 오버플로 검사를 없애고 인코딩 변경 후 빠른 재개를 가능하게 하며, 이전보다 훨씬 깊게 중첩된 객체 그래프의 직렬화를 허용합니다.
직렬화 깊이 제한에 대한 우려를 표현하며, V8에서 객체를 얼마나 깊게 직렬화할 수 있는지에 대한 궁금증을 드러냅니다.
V8에서 문자열은 1바이트 또는 2바이트 문자로 표현됩니다. ASCII 문자만 포함하면 1바이트 문자열로 저장되지만, 하나라도 ASCII 범위를 벗어나는 문자가 있으면 모든 문자가 2바이트 표현을 사용하게 되어 메모리 사용량이 두 배가 됩니다.
[00:05:58] SIMD·SWAR 기반 이스케이프 스캔

긴 문자열에는 ARM NEON SIMD를, 짧은 문자열에는 SWAR 비트 연산을 적용해 이스케이프 문자를 한 번에 검사하는 전략을 도입한 과정을 다룹니다.

통합 구현의 지속적인 분기와 타입 검사를 피하기 위해 전체 문자열화기가 문자 타입에 맞게 템플릿화되었습니다. 이로 인해 1바이트 문자열용과 2바이트 문자열용으로 각각 완전히 최적화된 두 개의 별개 직렬화기 버전을 컴파일합니다.
JSON에 이모지가 있으면 1바이트 문자열 최적화가 무용지물이 되므로 두 번 컴파일하는 방식을 채택했습니다. 특수 문자 유무에 따라 각각 최적화된 두 개의 완전한 직렬화기 구현이 존재합니다.
[00:07:07] 세그먼트 버퍼 도입

단일 큰 버퍼 재할당과 복사 비용을 없애기 위해 V8 zone 메모리에서 작은 세그먼트 버퍼를 연결해 쓰는 방식으로 메모리 오버헤드를 제거한 혁신을 소개합니다.

구현은 혼합 인코딩을 효율적으로 처리하며, 직렬화 중 각 문자열의 인스턴스 타입을 검사하여 빠른 경로에서 처리할 수 없는 표현을 감지합니다. constring은 문자열 덧셈 연산자로 만들어진 문자열 값을 나타내는 클래스입니다.
문자열을 연결할 때 생성되는 con string(constructed string)에 대한 설명. 이런 문자열들은 사이드 이펙트를 가질 수 있어서 직렬화 과정에서 특별한 검사가 필요하다.
constring의 플래트닝 과정에서 가비지 컬렉션이 트리거될 수 있는 이유를 설명. 새 문자열 생성 시 메모리 한계 초과나 이전 값들이 불필요해질 때 메모리 정리가 발생한다.
greet 함수를 예시로 들어 실제 상황 설명. 구독 버튼 클릭 요청도 함께 언급하며 친근한 톤으로 진행.
콘솔 로그 대신 message 변수로 정의하는 예시 제시. 대부분의 컴퓨터는 충분한 메모리를 가지고 있지만, 가정적으로 메모리 부족 상황을 상상해보라고 제안.
메모리 한계 초과 시 가비지 컬렉션이 트리거되는 과정 설명. V8 엔진이 더 이상 참조되지 않는 값들을 찾아 정리하는 메커니즘을 상세히 설명.
[00:08:44] Hidden Class로 객체 직렬화 가속

직렬화 후 객체의 hidden class에 플래그를 설정해 동일 구조 객체 반복 직렬화를 최적화합니다. 메모리 위치를 바로 읽어 문자열 버퍼로 복사하는 방법을 설명합니다.

문자열 연산이 지연 평가(lazy evaluation)된다는 새로 배운 지식 공유. 정의 시점이 아닌 실제 사용 시점에 평가되어 그때 가비지 컬렉션이 발생할 수 있다고 설명.
더 복잡한 예시로 발전시키며, 채팅의 Canada honk에게 조언을 구하는 등 상호작용적 요소 포함. 문자열 덧셈이 실제로는 아직 계산되지 않았을 수 있다고 설명.
console.log 실행 시점에서 실제 문자열 계산이 완료되고, 이전 값들이 정리 대상이 되는 과정 설명. JSON.stringify 호출이 실제 덧셈이 발생하는 시점임을 강조.
[00:09:49] JSON.parse 최적화 & 활용 팁

배열 내부 객체의 반복 파싱에도 hidden class 활용을 적용해 key 비교를 빠르게 하고, 객체 리터럴 대신 JSON.parse로 초기 객체를 정의하면 로딩 성능이 개선된다는 팁을 공유합니다.

JSON stringify 과정에서 지연 로딩(lazy evaluation)이 사용되어 더 이상 필요 없는 메모리를 정리하는 최적화 기법을 설명합니다. 이 과정에서 부작용이 발생할 수 있어 파싱과 문자열화 시 주의가 필요합니다.
로프 문자열(rope strings)이라는 일반적인 용어를 소개하고, 가비지 컬렉션 검사 시 1바이트 또는 2바이트 인코딩을 동시에 결정하는 효율적인 방법을 설명합니다.
[00:10:54] 제한 사항 및 파일 스루

fast path가 적용되지 않는 조건들을 정리합니다. replacer·space 인자 사용, 커스텀 toJSON, 인덱스 프로퍼티, 비순차 문자열 등 일반 목적 경로로 빠지는 사례를 짚습니다.

1바이트에서 2바이트 문자열화로의 전환이 비용 없이 가능한 메커니즘을 설명합니다. 기존 검사가 2바이트 문자열을 발견하면 새로운 문자열화기를 생성하여 최종 결과를 연결하는 방식입니다.
V8에서 JSON stringify의 깊이 한계가 4,000까지 확장되었다는 테스트 결과를 공유하며, 이렇게 높은 한계에 도달하는 것은 비정상적인 사용 패턴임을 경고합니다.
SIMD를 활용한 문자열 직렬화 최적화 기법을 소개합니다. JavaScript 문자열의 이스케이프 문자 검색을 위해 기존의 느린 문자별 루프 대신 2단계 전략을 사용합니다.
[00:11:54] 결론: V8 13.8(Chrome 138) 적용

고수준 로직부터 메모리 관리, 문자열/숫자 처리까지 전방위 최적화로 2배 이상 성능 개선을 달성했습니다. V8 13.8(Chrome 138)부터 적용된 이 변화가 실제 웹 애플리케이션에 미치는 가치를 정리합니다.

긴 문자열에 대해 ARM 64 같은 전용 하드웨어 SIMD 명령어를 사용하여 문자열의 큰 청크를 넓은 SIMD 레지스터에 로드하고, 여러 바이트의 이스케이프 문자를 한 번에 확인하는 고성능 기법을 설명합니다.
직접적인 어셈블리 작성은 아니지만 매우 저수준의 C 코드를 통해 메모리 레지스터를 직접 조작하는 SIMD 최적화 작업의 복잡성과 그에 대한 존경을 표현합니다.
JSON 직렬화의 놀라운 최적화에 대해 설명하며, 사람들이 동일한 콘텐츠를 500번 연속으로 최대 4,000 레이어까지 직렬화할 수 있다는 혼돈스러운 상황을 언급합니다.
SIMD(단일 명령어, 다중 데이터) 기술과 ARM 64 neon 구현에 대해 설명하고, 긴 문자열과 짧은 문자열에 대한 다른 최적화 접근법을 소개합니다.
swore(SIMD within a register) 기법을 설명하며, 이것이 표준 범용 레지스터에서 비트 연산 로직을 사용해 매우 적은 오버헤드로 여러 문자를 생성하는 방법임을 설명합니다.
JavaScript가 세상에서 가장 발전된 언어이고, V8의 JSON이 다른 완전한 프로그래밍 언어들보다 더 발전되고 기술적으로 철저하다는 점을 강조하며, 개발자들이 Rust 대신 JSON 최적화에 집중한다는 아이러니를 지적합니다.
소프트웨어 역사상 가장 어려운 엔지니어링 작업과 발전된 저수준 최적화가 Chrome V8에서 JSON을 빠르게 만들기 위해 이뤄졌다고 평가합니다.
V8의 코드 규모에 대해 논의하며, 160만 줄의 코드와 npm 패키지 수의 비교, JSC의 80만 줄, 그리고 주석과 테스트를 포함하면 거의 300만 줄에 달한다는 놀라운 사실을 공유합니다.
문자열 처리의 효율적인 방법을 설명하며, 청크별 스캔과 특수 문자가 없는 경우의 단순 복사 과정을 소개합니다.
빠른 경로의 고속 차선 최적화에 대해 설명하며, 메인 빠른 경로에서 더욱 빠른 고속 차선의 기회를 찾았다고 언급합니다.
객체 속성 반복과 키 검사 과정을 설명하고, 숨겨진 클래스에 플래그를 도입하여 fast JSON iterable로 표시하는 최적화 방법을 소개합니다.
V8 엔진의 히든 클래스에 대해 설명하며, JavaScript 계층에서는 직접 접근할 수 없지만 V8의 최적화를 위해 내부적으로 존재한다고 언급합니다.
히든 클래스 없이도 V8이 동작할 수 있지만, 지능적 설계 원리에 따라 히든 클래스가 필요하다고 설명합니다. V8은 객체들이 정형화된 패턴으로 사용될 것이라고 가정합니다.
JavaScript가 미리 컴파일되지 않는 스크립팅 언어이기 때문에 V8이 미래를 예측할 수 없지만, 반복되는 객체 형태를 감지하여 최적화된 구조체를 생성한다고 설명합니다.
Peak 함수 예제를 통해 조건부로 prominence 또는 experience 프로퍼티를 추가하는 코드를 보여주며, 이것이 어떻게 다양한 객체 형태를 만들어내는지 설명합니다.
루트 맵(초기 맵)부터 시작하여 name, height, 그리고 prominence/experience로 이어지는 맵 트리 구조를 설명합니다. 각 파란 박스가 하나의 맵을 나타낸다고 설명합니다.
맵들 간의 엣지에 있는 프로퍼티 이름들과 각 맵이 해당 객체의 프로퍼티 목록을 가진다고 설명합니다. 함수 실행 시점에 프로퍼티들이 순차적으로 추가되는 과정을 상세히 설명합니다.
조건문에 따라 다른 객체 형태가 만들어지며, 이것들이 키-값 저장소가 아닌 객체 형태의 매핑을 의미하는 맵이라고 설명합니다. 숫자를 전달했을 때의 히든 클래스 예시를 제시합니다.
백링크를 통해 초기 맵까지 추적할 수 있으며, 추가 정보와 함께 분석을 재시도합니다.
객체 필드 위치가 낮은 레벨에서 매핑되어 특정 키 요청 시 메모리 위치를 빠르게 찾을 수 있으며, name은 키 I, height는 키 I1, prominence나 experience는 키 I2를 가집니다.
매핑 완성을 위해 7개 객체가 필요하며, 완료되면 peak 객체는 3개 인오브젝트 속성을 가지고 추가 속성들은 백킹 스토어로 오프로드됩니다.
m2.cost 같은 추가 값은 새로운 매핑을 생성하며, 공통 형태의 객체는 빠른 매핑이 가능하지만 특별한 부분은 약간 더 비싼 조회를 필요로 합니다.
설명이 Oliver를 자극할 수 있지만 최선을 다하고 있으며, V8에 대한 많은 흥미로운 사실들로 몇 초 만에 뇌를 혼란스럽게 할 수 있습니다.
오리를 예시로 들어 설명하면, 오리는 이름, 키, 무게, 다리 등의 속성을 가지며, cost 필드는 셔츠처럼 오리에게 입힌 추가 요소로 분석 시 더 비용이 듭니다.
JS와 V8의 히든 클래스를 이해했으며, 같은 히든 클래스를 가진 객체 직렬화 시 첫 번째 이후에는 직렬화 방법을 알아 재계산 없이 빠르게 처리할 수 있습니다.
V8은 JSON 처리 최적화를 위해 메모리 위치와 히든 클래스를 활용하는 방법을 설명합니다. 각 필드의 메모리 위치를 알고 있기 때문에 가비지 컬렉션이나 부작용 걱정 없이 직접 메모리에서 문자열로 복사할 수 있습니다.
JSON.parse에도 최적화가 추가되어 배열 파싱 시 빠른 키 비교가 가능해졌습니다. 배열의 객체들이 같은 히든 클래스를 가진다고 가정하고 활용하는 방식입니다.
JavaScript와 V8의 흥미로운 사실을 소개합니다. 논란의 여지없는 실제 사실로, 많은 경우 객체를 인라인으로 정의하는 것보다 문자열로 JSON.parse에 전달하는 것이 더 빠르다고 합니다.
JSON 문법이 JavaScript보다 간단하기 때문에 더 효율적으로 파싱됩니다. 이는 대용량 JSON 구성 객체를 사용하는 웹 앱의 시작 성능 개선에 활용할 수 있으며, Redux 스토어 같은 경우가 좋은 예시입니다.
복잡한 객체가 애플리케이션 시작점이 되는 경우, JavaScript는 파싱할 것이 너무 많아 JSON보다 느립니다. JSON은 문자열, 숫자, 객체, 불린 등 제한된 타입만 가지고 있어 파싱이 매우 효율적입니다.
JSON 파싱이 이제 더욱 빨라진 이유를 설명합니다. 히든 클래스를 기반으로 메모리 위치와 레지스터를 재매핑하고 재사용하는 방법을 찾았기 때문입니다.
이런 최적화가 앱 속도에 큰 영향을 주지는 않지만, 모든 부분을 최적화하고 싶다면 Chrome 프로파일러를 통해 값 할당에 시간이 소요되는 부분을 JSON으로 시도해볼 것을 제안합니다.
새로운 최적화로 더 빠른 double-to-string 알고리즘을 소개합니다. 숫자를 문자열로 변환하는 것은 놀랍도록 복잡하고 성능에 중요한 작업으로, JSON stringify 작업의 일환으로 오래된 Gris 3 알고리즘을 Dragon Box 알고리즘으로 교체했습니다.
숫자를 문자열로 변환하는 복잡성에 대해 설명하며, V8에서 오랜 기간 사용하던 방식을 Dragon Box라는 새로운 알고리즘으로 교체했다고 언급합니다.
Dragon Box는 부동소수점 이진수를 십진수 문자열로 변환하는 방법에 관한 이론적 연구로, C++로 구현되어 V8에 적용되었습니다.
Azam.js와 웹 어셈블리 초기 시절에 대한 회상을 나누며, JavaScript에서 어셈블리를 직접 사용하는 대신 WASM을 사용해야 한다는 의견을 제시합니다.
JSON stringify 프로파일링으로 시작된 최적화가 V8 전체의 number.prototype.toString 호출에 성능 향상을 가져다준다고 설명합니다.
기존의 단일 연속 버퍼 방식의 문제점을 지적하며, 버퍼 공간 부족 시 재할당과 복사로 인한 성능 오버헤드가 큰 문제였다고 설명합니다.
연속 버퍼의 한계를 인식하고 분할된 버퍼 시스템으로 교체하여, V8 존 메모리에 작은 세그먼트들을 사용하는 새로운 접근법을 도입했다고 설명합니다.
V8 엔진의 JSON 직렬화 최적화에 대해 설명하며, 새로운 세그먼트 할당 방식이 비용이 많이 드는 복사 작업을 완전히 제거할 수 있다고 합니다. 이런 깨달음들이 소프트웨어 혁신의 원동력이 되어왔다고 강조합니다.
개발자들의 사고 과정을 예시로 들며, 모든 데이터를 한 곳에 저장하지 말고 여기저기 분산 저장 후 마지막에 합치는 아이디어를 소개합니다. Canada Honk의 지적을 인용하며 코드 실수가 샌드박스 탈출과 원격 코드 실행으로 이어질 수 있는 위험성을 경고합니다.
GPT-5를 이용한 실험을 제안합니다. 인터넷 접속을 차단한 상태에서 V8 엔진의 JSON.stringify 성능 개선 방법을 묻고, AI가 어떤 이론적 해결책을 제시할지 궁금해합니다.
새로운 고속 경로의 한계점을 설명합니다. 이 최적화는 일반적이고 단순한 경우에만 적용되며, 조건에 맞지 않으면 범용 직렬화기로 돌아간다고 합니다. replacer나 space 인수 같은 고급 기능을 사용하면 속도 최적화에서 배제된다고 설명합니다.
최적화 조건을 더 자세히 설명합니다. 순수한 데이터 객체와 배열이어야 하고, 커스텀 toJSON 메서드가 있으면 안 된다고 합니다. 표준 프로토타입을 가정하며, 커스텀 직렬화 로직이 없어야 한다고 강조합니다.
toJSON 오버라이드에 대한 경고를 합니다. 이 기능을 처음 알게 된 사람들에게 사용하지 말라고 강력히 권고하며, 본인의 경험을 바탕으로 그럴 가치가 없다고 조언합니다. 두 값을 더할 때의 동작도 오버라이드할 수 있다는 위험한 기능에 대해서도 언급합니다.
V8 JSON 직렬화 최적화의 주의사항들을 설명합니다. 객체에 배열 형태의 인덱스 속성을 사용하면 느린 직렬화기가 작동하며, 간단한 문자열 타입이 성능에 유리하다고 강조합니다.
API 응답이나 구성 객체 캐싱 같은 일반적인 사용 사례에서는 최적화 조건이 자연스럽게 충족되어 개발자들이 자동으로 성능 향상을 누릴 수 있다고 설명합니다.
GPT-5가 V8 최적화 방법을 예측한 내용을 보여줍니다. 안정적인 맵, 딕셔너리 모드 없음, SIMD 벡터화 등 실제 최적화 기법들을 놀랍도록 정확하게 추측했다고 감탄합니다.
AI가 V8 문자열 직렬화 성능 향상을 대부분 맞춰서 놀라워하며, 벤치마크 결과를 확인합니다. Pixel 9이 M1 Mac보다 빠른 결과에 당황해하면서도 점수 기준임을 확인합니다.
JavaScript는 빠른 언어가 아닙니다.
우리 모두 이걸 알고 이해하고 있죠.
하지만 JSON이 얼마나 빠른지는
놀라실 거예요. 특히 JSON.parse와
stringify 말이죠. 역사적으로 이건
JSON을 실제 객체로 변환하는
최고의 구현 중 하나였습니다. 당연하죠.
왜 안 그렇겠어요? 이건 말 그대로
JavaScript 객체 표기법이잖아요.
핵심은 이게 직렬화된
JavaScript 객체라는 거예요.
당연히 상호 변환이 가능하죠. 하지만
이게 두 배 빨라졌다고 하면 어떨까요?
음, 그건 거짓말이겠죠. 왜냐하면
많은 시나리오에서 실제로는 거의
3배 빨라졌거든요. Chrome 팀이
JSON stringify를 더욱 빠르게 만들기 위해
방금 투입한 작업에 완전히 압도당했습니다.
작고 간단해 보이지만, 실제로는
여기 파고들 재미있는 내용이 많아요.
JSON stringify의 구현부터
이런 종류의 작업에서 V8의 성능 관련
이상한 특성들까지, 그리고
제가 가장 좋아하는 재미있는 사실 중 하나는
많은 객체들이 실제로 객체를
직접 정의하는 것보다 JSON으로 정의한 후
JSON.parse하는 게 VM에서 더 빠르게
로드된다는 거예요.
여기 파고들 재미있는 내용이 정말 많습니다.
사람들이 단순히 우리가
매일 의존하고 있는 이런 간단한 것들을
빠르게 만들기 위해 JSON에 얼마나
많은 작업이 들어가는지, 그리고 대부분의
사용 사례에서 얼마나 빠른지
제대로 평가하지 못한다는 걸 느껴요.
이 변경사항처럼, 제 비디오도 부작용 없이
유지하고 싶지만, 더 중요한 건
일반적으로 무료로 유지하고 싶다는 거예요.
누군가는 비용을 지불해야 하죠.
그러니까 오늘 스폰서 소개를 간단히 하고
본격적으로 들어가겠습니다.
AI로 애플리케이션을 구축하는 게
이보다 쉬웠던 적은 없었어요. 작은 애플리케이션과
작은 코드베이스를 구축하는 한에서 말이죠.
큰 프로젝트를 시도하면 빠르게
무너지기 시작해요. 그래서 오늘의 스폰서인
Augment Code가 그렇게 멋진 거죠.
이 사람들은 실제 직장에서
작업하고 있는 것 같은 거대한 컨텍스트를
관리하는 방법을 알아냈어요.
많은 대기업들이 이동하기 시작했고
저도 직접 확인했는데, 우리가
Augment와 몇 번의 광고만 진행했는데도
정기적으로 여러분들이 가장 좋아하는
스폰서 목록에서 1위를 차지하고 있어요.
그들에 대한 반응이 얼마나 긍정적인지
믿을 수 없었는데, 직접 사용해보니
말이 되더라고요. 정말 엄청나게
좋거든요. 정말 그렇게 좋아요.
최근에 백그라운드 에이전트를 도입해서
클라우드에서 PR을 자동으로 열어줄 수 있어요.
이런 다른 도구들을 시도해봤는데
저는 실제로 신뢰하지 않았어요.
제 컴퓨터에서 실행할 수 없다면
어떤 결과든 나올 거라고
신뢰하지 않거든요. 하지만 이들은
다른 모든 도구와 달리 전체
코드베이스의 컨텍스트를 가지고 있어서
올바른 결과를 얻을 가능성이 훨씬 높아요.
그리고 SWEBench에서 어떻게
압도적인 성과를 거뒀는지 보면 알 수 있어요.
현재까지 최고 점수입니다. 이들은
코드 관련 어려운 문제를 해결하는 데
정말, 정말 뛰어난 걸 만들었어요. Uber,
Lemonade, Vercel 등의 회사들이 사용하고 있습니다.
Augment를 오늘 사용하고 있어요. 솔직히 말해서, 저도
거대한 코드베이스에서 무언가를 찾을 때 더 많이 사용하고 있습니다.
큰 오픈소스 코드베이스에서
뭔가가 어떻게 작동하는지 알아내고 싶을 때,
이게 제가 하는 방식입니다.
홈페이지의 후기가 모든 것을 말해주죠.
Augment는 정말 독보적인 수준입니다.
다른 모든 확장 프로그램들은 비교도 안 되죠.
안정성, 인덱싱, 메모리,
여러분의 코드베이스를 정말 잘 이해합니다.
영원히 사용할 수 있었으면 좋겠어요.
Augment의 모든 기능은
실제로 대규모 소프트웨어를 배포하는 사람들이 만든 것 같아요.
네, 만약 여러분이 취미로만 코딩한다면 이건 건너뛰셔도 됩니다.
하지만 진짜 소프트웨어를 개발하고 있다면
오늘 한번 확인해보세요.
sov.link/augment에서요.
아, 이 변화가 정말 기대됩니다.
JSON을 여러 곳에서 사용하는데 이게 정말 재미있을 것 같아요.
JSON.stringify는 데이터를 직렬화하는
핵심 JavaScript 함수입니다.
다른 용도로 뭘 사용하는지는 묻지도 않겠어요.
답변이 마음에 들지 않을 테니까요.
답변이 있으시면 공유해주시고
제 하루를 망쳐주세요.
성능이 웹 전반의 일반적인 작업에
직접적으로 영향을 미칩니다.
네트워크 요청을 위한 데이터 직렬화부터
로컬 스토리지에 데이터 저장까지,
더 빠른 JSON stringify는
더 빠른 페이지 상호작용과
더 반응성이 좋은 애플리케이션으로 이어집니다.
그래서 최근의 엔지니어링 노력으로
V8의 JSON stringify가
두 배 이상 빨라졌다는 소식을 공유하게 되어 기쁩니다.
이 포스트는 이러한 개선을
가능하게 한 기술적 최적화를 분석합니다.
오, 저주받은 사용 사례들이 나오네요.
이건 Porfurer의 제작자,
JSON과 JavaScript 마법사가 제공한 거예요.
객체 복제하기. JSON.parse
JSON 문자열을 파싱하는 거죠. 아.
깊은 복제를 위한 일반적인 패턴이에요.
아, 정말 아파요.
기억하세요, 우리는 부작용이 없는 빠른 경로를 원합니다.
정말 빠르고
이상한 부작용을 일으키지 않는 걸 원해요.
이 최적화의 기반은 간단한 전제에서
구축된 새로운 고속 경로입니다.
객체를 직렬화하는 것이
부작용을 일으키지 않는다는 것을 보장할 수 있다면
훨씬 빠른 특화된 구현을
사용할 수 있습니다.
이 맥락에서 부작용이란
객체의 간단하고 효율적인 순회를
방해하는 모든 것을 의미합니다.
이것은 직렬화 중에 사용자 정의 코드를
실행하는 명백한 경우뿐만 아니라
가비지 컬렉션 사이클을 유발할 수 있는
더 미묘한 내부 작업도 포함합니다.
정확히 무엇이 부작용을 일으킬 수 있고
어떻게 피할 수 있는지에 대한 자세한 내용은
제한사항을 참조하세요.
생각이 많습니다.
V8의 가비지 컬렉션은, 제가
이전 비디오에서 우연히
작성자가 틀린 부분을 정확히 추측해서
수정을 발행해야 했던 것처럼요.
그것도 재미있었죠.
이번에는 어떻게 될지 보겠습니다.
이게 제가 평소보다 더 깊이 들어가는
내용일 수도 있고 제가 바보같이 보일 수도 있지만
알아볼 방법은 하나뿐이죠.
V8이 다음을 확인할 수 있는 한
직렬화 과정에서 이러한 사이드 이펙트가 없다면
고도로 최적화된 경로를 유지할 수 있습니다.
이를 통해 많은 비용이 큰 검사와
범용 직렬화기에서 필요했던
방어적 로직을 우회할 수 있어서
상당한 속도 향상을 가져옵니다
가장 일반적인 타입의
평범한 데이터를 나타내는
자바스크립트 객체들에 대해서 말이죠.
또한, 새로운 빠른 경로는
재귀적인 범용 직렬화기와 달리
반복적입니다. 이런 아키텍처 선택은
스택 오버플로 검사의 필요성을 없애고
인코딩 변경 후 빠르게 재개할 수 있게
해줄 뿐만 아니라, 개발자들이
훨씬 더 깊게 중첩된
객체 그래프를 직렬화할 수 있게 해줍니다
이전에는 불가능했던 수준으로 말이죠. 오 안돼. 오 안돼.
직렬화 깊이 제한이 뭔지
무서워지기 시작했어요. 캐나다 honk.
분명 답을 알고 있을 거예요.
V8에서 객체를 얼마나 깊게
직렬화할 수 있을까요? 문제가 생기기 전에 말이죠?
속으로 울고 있어요. 그거면 충분해요.
듣고 싶었던 게 바로 그거예요.
진짜 답은 필요 없어요.
답을 아신다면 코멘트로 남겨주세요.
하지만 제가 찾던 정보는 얻었어요.
다양한 문자열 표현 처리하기.
V8에서 문자열은 1바이트 또는
2바이트 문자로 표현될 수 있습니다. 아, 맙소사.
문자열이 ASCII 문자만 포함한다면
V8에서는 1바이트 문자열로 저장되어
문자당 1바이트를 사용합니다. 하지만
문자열에 ASCII 범위를 벗어나는
문자가 단 하나라도 있으면
문자열의 모든 문자가
2바이트 표현을 사용하게 되어
메모리 사용량이 본질적으로
두 배가 됩니다. 제가 끔찍한 일을 했네요.
이제 올리버가 V8 소스코드를
보게 만들어버렸어요. 정말 죄송합니다.
이건 정말 재미있는 큰 단어들로 가득해요.
통합 구현의 지속적인 분기와
타입 검사를 피하기 위해
전체 문자열화기가 이제 문자 타입에 맞게
템플릿화되었습니다. 즉, 우리는
두 개의 별개로 특화된
직렬화기 버전을 컴파일합니다.
하나는 1바이트 문자열에
완전히 최적화되고, 다른 하나는
2바이트 문자열에 완전히 최적화됩니다.
이는 바이너리 크기에 영향을 주지만
증가된 성능이 확실히
그만한 가치가 있다고 생각합니다.
이건 정말 흥미로워요. JSON에 이모지가 있으면
1바이트 문자열에 대한 최적화는
도움이 되지 않거든요. 대신에
두 번 컴파일했습니다. 이제
직렬화기의 완전한 구현이 두 개 있어요.
하나는 특수 문자가 없는 경우이고
다른 하나는 특수 문자가 있는 경우입니다.
좀 재미있네요. 자바스크립트 개발자들은
어떤 문제든 해결책을 설치하죠, 그렇죠?
구현은 혼합 인코딩을
효율적으로 처리합니다. 직렬화 중에
각 문자열의 인스턴스 타입을 이미 검사해서
빠른 경로에서 처리할 수 없는
표현을 감지해야 합니다.
constring이 뭔지 아세요? Constring 클래스는
문자열에 덧셈 연산자를 사용해서
만들어진 문자열 값을 설명합니다.
아, 재미있네요. 그냥 문자열들을
무작정 더할 수 있잖아요. 그럴 때
그렇게 하면 이제 con string이 생깁니다. 아마
constructed string일 거라고 생각하는데,
이런 것들은 사이드 이펙트를 가질 수 있습니다.
그래서 직렬화 과정에서 우리는 각 문자열의
인스턴스 타입을 미리 검사해서 해당 표현이
빠른 경로에서 처리될 수 없는지 감지해야 합니다.
왜냐하면 다시 말하지만 constring은
플래트닝 과정에서 가비지 컬렉션을
트리거할 수 있기 때문입니다. 더 이상
새 문자열을 만들 때 추가했던 이전 값들이
필요하지 않거나, 새 문자열이
메모리 한계를 초과하게 만든다면
가비지 컬렉션이 들어와서
이전에 사용되지 않았거나 더 이상
사용되지 않는 메모리를 정리해야 합니다.
왜냐하면 당신이 이 새로운 값을
정의했기 때문입니다. 우리가 여기서 말하는 것에 대한
간단한 예시로, greet 함수가 있습니다.
greet 함수는 우리가 이미 정의한
이 con을 사용합니다. 빈 문자열과
당신이 전달하는 name입니다. 그리고 우리는
제 구독자 중 한 명을 인사합니다.
말하자면, 아직 구독하지 않으셨다면
비용은 전혀 들지 않습니다. 버튼이
영상 바로 아래에 있습니다. 그냥 클릭하세요.
저희에게 도움이 됩니다. 그냥 콘솔 로그 대신
이것을 message로 정의한다고 상상해봅시다.
그리고 console.log message를 합니다.
이제 여기서 제 말에 귀 기울여 보세요. 구형 아이폰 같은
것들을 제외하고는 대부분의 컴퓨터에
이 모든 텍스트 내용을 문제없이 담을 만큼
충분한 메모리가 있다는 걸 알고 있습니다. 하지만
가정적으로 이것들을 더할 때
메모리가 부족해진다고 해봅시다.
이것들을 추가해서 message를 생성할 때
메모리 한계를 초과하게 됩니다.
이것이 가비지 컬렉션을 트리거해서
V8 엔진이 더 이상 참조되지 않는
모든 것들을 찾아가서
참조를 해제할 것입니다. 사용되지 않는
다른 값들이나 더 이상 실행되지 않는
정의들을 버릴 수 있습니다.
우리가 아직 name이 존재하는
스코프 내에 있기 때문에, 이 문자열이
정리되도록 놔둘 만큼 엔진이 똑똑하다고는 생각하지 않지만,
가정적으로는 가능합니다. 여기서 요점은
이 새로운 문자열의 생성이, 특히
소비될 때인데 제가 방금 배운 바로는 이것들이
실제로는 지연 평가된다는 것입니다.
그래서 정의할 때는 소비되지 않습니다.
사용할 때 평가되는데, 그 시점에
가비지 컬렉션이 발생할 수 있습니다.
그래서 한 단계 더 나아가 봅시다.
실제로는 message를 반환하고
const some message equals
greet one of my subscribers 이렇게 합니다.
그리고 다시, 제가 틀렸다면 정정해 주세요,
Canada honk, 채팅에서 봅니다.
정말 도움이 많이 되고 있어요. 이
덧셈이 실제로는 아직 계산되지
않았을 수도 있습니다. 그래서 이 시점에서
console log some message를 합니다.
이제 계산이 완료될 것입니다.
문자열이 하나로 합쳐질 것이고,
여기서 전달한 name은 이 코드가
이미 실행되었기 때문에 더 이상 관련이 없습니다.
그래서 이것은 이제 정리 대상으로
표시됩니다. 그래서 제가 실제로
이것을 사용할 때, 또는 우리가 신경 쓰는 경우인
const JSONified equals
JSON.stringify
some message, 이것이 바로
덧셈이 실제로 발생하는 시점입니다.
이 파일에는 다른 것들이 있는데
이 파일에 있는 다른 것들은 이제
이 시점에서는 더 이상 관련이 없습니다. 우리는
더 이상 이 문자열에 대해 신경 쓰지 않습니다
왜냐하면 이미 추가되고
계산되어 여기에 있기 때문입니다. 지연 로딩으로
처리됩니다. 하지만 더 중요한 것은, 지연 로딩될 때
이제 더 이상 중요하지 않은 것들을
정리할 기회가 생긴다는 것입니다
그래서 이 JSON stringify를 호출할 때
메모리에 여전히 남아있을 수 있는
이 문자열이 이제는 더 이상
있을 필요가 없어집니다. 하지만 이것도 일종의
부작용이기 때문에
파싱할 때와 문자열화할 때
신경 써야 합니다. 만약 부작용이 있다면
이 경우에는 있을 텐데
이제 조금 다르게 처리해야 합니다
도움 주셔서 다시 한번 감사합니다
일반적인 용어는 로프 문자열입니다. 알아두면
정말 좋을 것 같네요. 그리고 이것이
가비지 컬렉션을 트리거할지 확인하는 검사도
1바이트 또는 2바이트 인코딩을
사용해야 할지도 확인합니다
정말 편리한 일석이조죠. 이 때문에
낙관적인 1바이트 문자열화에서
2바이트 버전으로 전환하는 결정이
본질적으로 공짜가 됩니다
기존 검사가 2바이트 문자열을 발견하면
새로운 2바이트
문자열화기가 생성되어 현재 상태를
상속받습니다. 마지막에 최종
결과는 단순히
초기 1바이트 문자열화기의 출력과
2바이트의 출력을
연결하여 구성됩니다. 아, 그래서
그들도 연결 문자열을 만들고 있군요
이 전략은 일반적인 경우에 대해
높은 최적화 경로를 유지하면서도
2바이트 문자 처리로의 전환을
가볍고 효율적으로 보장합니다
그리고 캐나다는 이제 그의
테스트를 완료했습니다. 올리버에 따르면
JSON stringify에서 4,000 깊이까지
V8이 깨지기 전까지 갈 수 있었다고 합니다
이제 한계가 더 높아진다는 것을 알게 되어
무섭습니다. 왜냐하면 그 한계에 도달한다면
뭔가 잘못된 일을 하고 있는 것이고
이제 더 오랫동안 잘못된 일을 할 수 있게 되었습니다
최적화로 돌아가서. SIMD로
문자열 직렬화 최적화하기
JS의 모든 문자열은 JSON으로
직렬화할 때 이스케이프가 필요한
문자를 포함할 수 있습니다. 따옴표나
백틱 같은 것들 말이죠. 이들을 찾기 위한
전통적인 문자별 루프는
느립니다. 가볍게 말해서 느릴 것입니다
이를 가속화하기 위해 문자열
길이에 기반한 2단계 전략을
사용합니다. 긴 문자열의 경우
ARM 64와 같은 전용 하드웨어 SIMD 명령어로
전환합니다. 이를 통해 문자열의
훨씬 큰 청크를 넓은 SIMD 레지스터에
로드하고 여러 바이트를 한 번에
모든 이스케이프 문자에 대해
몇 개의 명령어로 확인할 수 있습니다
네, 그들은 이것을 위해 어셈블리를 작성했습니다
FFmpeg 개발자들이 자랑스러워할 만합니다
저걸 보세요
음, 사실 직접 어셈블리를
작성하는 것은 아니지만
매우 저수준의 C 작업을 하며
메모리 레지스터를 직접 건드리고 있습니다. 정말 재미있네요
SIMD 작업은 정말 혼돈입니다. 하지만
그들이 해낸 것에 대해 존경합니다. 그들이
이 모든 최적화를 해냈다는 것을 상상해보면
사람들이 동일한 콘텐츠를
500번 연속으로, 최대 4,000 레이어까지
직렬화할 수 있다니. 정말 혼돈이야. 이게 좋은 설명이네,
여기에 올려놓을게. 고마워, NMG.
SIMD는 단일 명령어,
다중 데이터를 의미해. ARM 64 neon은
ARM SIMD
명령어의 구현이야. 네, 내가 아는 한
맞아. 그건 긴 문자열에 대한 얘기고.
하지만 하드웨어 명령어의
설정 비용이 너무 높은
짧은 문자열에 대해서는
swore라는 기법을 사용하는데, 이는 SIMD within a
register야. 이 접근법은 영리한
비트 연산 로직을 표준 범용
레지스터에서 사용해서 매우 적은
오버헤드로 한 번에
여러 문자를 생성해. 와, 진짜.
JavaScript가 세상에서 가장 발전된
언어라는 게 좀 웃기고
여러 면에서 V8의 JSON이 다른 완전한
프로그래밍 언어들보다 더 발전되고
기술적으로 철저하다는 거야. 이 사람들은
Rust를 만들 수도 있었는데 대신
우리 JSON stringify를 더 빠르게 만들고 있어.
아름다워. 정말 멋져. 가장
어려운 엔지니어링 작업, 가장
발전된 저수준
최적화 중 일부는 소프트웨어
역사상
Chrome의 V8에서 JSON을 더 빠르게 만들기 위해 이뤄졌어.
V8이 몇 줄의 코드인지 알아? 방금
Canada Honk가 물어봤는데 나는
알기가 무서워. 이거 재미있는 사실이
바로 나올 준비가 되어있네, 그렇지?
곧 속이 안 좋아질 것 같아. 숫자만
말해봐. 160만 줄. 그러니까 우리가 가진 npm 패키지는
V8 코드 줄 수보다
2배 더 많아. 하지만 그 둘이
어느 정도 비슷하다는 사실이 정말 웃겨. 그리고 JSC는
80만 줄이야. 젠장 진짜.
V8의 번들은 9메가바이트야. 그러니까
컴파일된 소스가 대략
9메가가 나와. 와. 만약에
주석, 추가 코드,
테스트, 그리고 모든 걸 포함하면 전체 줄 수는
거의 300만 줄이라고 해. 어떤 정보는
모르는 게 나아. 정말 아파. 헤이, 적어도
우리는 이제 자바스크립트
객체를 더 빨리 직렬화할 수 있어.
어떤 방법을 사용하든, 이 과정은
매우 효율적이야. 우리는 빠르게
문자열을 청크별로 스캔해. 만약
청크에 특수 문자가 없다면,
이게 일반적인 경우인데, 간단히
전체 문자열을 복사할 수 있어. 이제
빠른 경로에 고속 차선이 생겨서
더욱 최적화를 계속하고 있어.
메인 빠른 경로에서도 우리는
더욱 빠른
고속 차선의 기회를 찾았어. 기본적으로 빠른 경로는
객체의
속성을 반복하고, 각 키에 대해
일련의 검사를 수행해야 해. 키가
심볼이 아닌지 확인하고.
열거 가능한지 확인하고, 마지막으로
이스케이핑이 필요한 문자가 있는지
문자열을 스캔해. 이를 제거하기 위해
객체의 숨겨진 클래스에
플래그를 도입했어. 객체의 모든
속성을 직렬화하고 나면, 속성 키가
심볼이 아닌 경우 숨겨진 클래스를 fast JSON iterable로 표시해. 모든 속성은
이제 열거 가능하고 어떤 속성 키도
이스케이핑이 필요한 문자를 포함하지 않아
이스케이핑이 필요해요. 멋지네요. V8의 숨겨진
클래스들을 좋아할 수밖에 없어요. 이런 구현
세부사항들이 정말 많은데, 우리가
JavaScript 계층에서 반드시 접근할 수 있는
것은 아니지만, 내부적으로는 존재해서
V8이 최적화를 수행할 수 있게 해줍니다.
V8은 히든 클래스 없이도 동작할 수는 있어요.
물론 그렇다면 각 객체를 단순히
프로퍼티들의 모음으로 처리하겠죠.
하지만 매우 유용한 원리 하나가
남겨져 있을 겁니다.
지능적 설계의 원리죠. 즉,
히든 클래스가 없다면
비지능적이라고 말하는 것과 같습니다.
V8은 여러분이 서로 다른 종류의
객체를 그렇게 많이 생성하지는
않을 것이며, 각 종류의 객체는
결국 정형화된 방식으로
사용될 것이라고 추정합니다. 제가 '결국'
이라고 한 이유는 JavaScript가
스크립팅 언어이지
미리 컴파일되는 언어가 아니기 때문입니다.
따라서 V8은 다음에 무엇이 올지 절대 알 수 없어요.
그래서 만약 여러 번 사용하는
객체 형태가 있다면, V8이 이를 알아차리고
그에 대한 구조체를 생성해서
이 객체 형태를 알고 식별하여
매핑하고 더 효율적으로 사용할 수 있게 됩니다.
여기 함수가 하나 있는데
이 함수에서는 this를 사용합니다.
절대 하지 말아야 할 일이지만 할 수는 있어요.
만약 extra 데이터가 숫자가 아니라면
experience에 넣고, 그렇지 않으면
prominence에 넣습니다. 매우 흥미롭네요.
new peak을 호출해서 만든 두 개의 객체가
있는데 둘 다 name과 height을 가집니다.
하지만 이쪽은 숫자를 전달했기 때문에 prominence를 가지고
저쪽은 문자열을 전달했기 때문에
experience를 가집니다. 이 코드로
우리는 이미 루트 맵부터 시작하는
흥미로운 맵 트리를 얻었습니다.
이는 초기 맵이라고도 알려져 있으며
peak 함수에 연결되어 있습니다. 먼저
name으로 매핑하고, 그 다음
height로 매핑합니다. 그리고 이제 prominence와
experience로 두 가지 다른
매핑을 가지게 됩니다. 각각의 파란 박스는
초기 맵부터 시작하는 맵입니다.
이것은 만약 어떻게든 peak 함수를
단일 프로퍼티도 추가하지 않고
실행하게 된다면 반환되는 객체의 맵입니다.
후속 맵들은 맵 사이의
엣지에 있는 이름으로 주어진
프로퍼티들을 추가함으로써 생성되는 것들입니다.
각 맵은 해당 맵의
객체와 연관된 프로퍼티들의 목록을 가집니다.
그리고 왜 이 모든 것들에
중지점이 있는지 궁금하시다면,
함수가 실행될 때 프로퍼티들이
아직 정의되지 않았기 때문입니다.
그냥 함수만 실행되는 거죠. 따라서
this에 연결된 값이 없는 상태로 시작해서
name을 가지게 됩니다. 이제 우리는
아무것도 없는 버전과 name을 가진 버전이 있습니다.
이제 height을 가진 세 번째 버전도 있고
이제 네 번째나 다섯 번째 버전도 있습니다.
둘 다일 수는 없어요. 이 조건에 따라
둘 중 하나입니다. 이것들이 바로
맵입니다. 맵 키-값 저장소가 아니라, 이런
형태들에 존재할 수 있는 다양한
키들의 매핑을 의미합니다. 이런 맵들 중
하나인 맵 3을 예로 들면, 이는
extra 인수에 숫자를 전달했을 때
얻게 될 객체의 히든 클래스입니다.
peak 함수의 추가 인자에서 전달된 것입니다. 백링크를 따라
초기 맵까지 올라갈 수 있습니다.
추가 정보와 함께
다시 해보겠습니다.
흥미롭습니다. 핵심은
객체 필드 위치가 낮은 레벨에서 매핑되어
특정 키를 요청했을 때
메모리에서 그 위치를
더 빠르게 찾을 수 있다는 것입니다. 그래서 name은
키 I, height는 키 I1을, 그리고
prominence나 experience는 키 I2를 가집니다.
매핑을 완성하려면
7개의 객체를 생성해야 합니다.
완료되면 peak 객체는
정확히 3개의 인오브젝트 속성을 가지며
객체에서 직접 더 추가할 수 없습니다.
추가 속성들은
객체의 속성 백킹 스토어로
오프로드됩니다.
이것은 속성 값들의 배열로
인덱스는 맵에서 가져옵니다. 음
엄밀히 말하면 기술 배열에서 말이죠.
만약 m2.cost같은
추가 값을 넣으면
이는 cost를 이 일반 키의
상수로 갖는 새로운 매핑을 생성합니다.
이렇게 하는 이유는
객체에 공통 형태가 있을 때
키를 메모리 저장 위치에
훨씬 빠르게 매핑할 수 있기 때문입니다.
이 경우 cost처럼
형태에 추가된 특별한 부분들은
조회가 약간 더 비싸지지만
실제 문제를 일으킬 정도로
성가시지는 않습니다.
제가 이를 설명하려는 시도가
Oliver를 꽤 자극하고 있을 겁니다.
최선을 다하고 있습니다. 사실적
오류가 있으면 알려주세요.
V8에 대한 재미있는 사실들이 많아서
몇 초 만에 뇌를 아프게 할 수 있습니다.
상상조차 할 수 없네요. 꽤 잘하고 있습니다.
정말 빨리 망가뜨려보겠습니다.
누군가 오리를 언급했는데, 그건 할 수 있습니다.
클래스와 객체 정의에
동물들을 사용하는 걸 좋아하죠? 오리는
이름, 키, 무게나
다리 등을 가집니다.
이 cost 필드, 새로운 P0
추가된 필드를 셔츠처럼 생각할 수 있습니다.
오리의 일부는 아니지만
오리에게 준 것입니다. 그래서 이제
오리와 함께 있지만, 오리를
분석하려 할 때, 이제 이것은
발의 개수를 세는 것보다 비쌉니다.
이제 JS, 특히 V8의
히든 클래스를 대략적으로 이해했습니다.
이전에 직렬화했던 객체와
같은 히든 클래스를 가진 객체를 직렬화할 때
이는 매우 일반적입니다. 예를 들어
모든 객체가 같은 형태를 가진
객체 배열에서 말이죠.
같은 타입의 사용자들로 구성된
사용자 배열을 직렬화하려고 한다면
첫 번째 이후에는 히든 클래스를 찾고
어떻게 직렬화되는지 알 수 있습니다.
그래서 이 직렬화 인식을 사용해서
배열의 다른 모든 것들에 대해
다시 계산할 필요가 없습니다.
이를 알고 있고 빠른
JSON 반복 가능 클래스라면
추가 검사 없이
모든 키를 문자열 버퍼에 복사할 수 있습니다.
다시 우리가 알고 있기 때문에 정말 멋집니다.
이 각 필드들의 메모리 위치를 알고 있습니다.
그래서 map 3이나 4에서도 특별한 작업을 할 필요가 없다는 것을 알고 있다면,
가비지 컬렉션이나
부작용을 걱정할 필요가 없기 때문에
전혀 신경 쓸 필요가 없습니다.
우리는 말 그대로
그 메모리 위치에서 바로 새로운 문자열로 가져올 수 있습니다.
정말 멋지죠. 우리는 또한
JSON.parse에 이 최적화를 추가했습니다.
배열을 파싱할 때 빠른 키 비교를 위해 활용할 수 있도록
했는데, 배열의 객체들이
종종 같은 히든 클래스를 가진다고 가정하고 있습니다.
이건 정말 대단한 일이고,
이를 통해 JavaScript와 V8에서 제가 가장 좋아하는
재미있는 사실 중 하나로 넘어가겠습니다.
이건 논란의 여지가 없는 사실입니다.
실제로 많은 경우에
객체를 문자열로 JSON.parse에 전달해서 정의하는 것이
객체를 인라인으로 정의하는 것보다
더 빠르다는 것입니다.
우리가 항상 함께 작업하는
언어와 엔진에서 제가 가장 좋아하는 특이한 점 중 하나입니다.
JSON 문법이
JavaScript 문법보다 훨씬 간단하기 때문에,
JSON은 JS보다
더 효율적으로 파싱될 수 있습니다.
이 지식은 대용량 JSON과 유사한
구성 객체를 전달하는
웹 앱의 시작 성능을
개선하는 데 적용할 수 있습니다.
인라인 Redux 스토어와 같은
리터럴 말이죠. 이렇게 인라인으로 하는 대신,
JSON에 넣을 수 있습니다.
이게 얼마나 혼란스러운지 이해하시겠어요?
Redux를 예시로 들었다는 것이요?
애플리케이션의 시작점이 되는
복잡한 객체가 있고,
다양한 키들과
복잡한 배열들 그리고 다른 모든 것들이 있다면,
JavaScript는 파싱할 것이 너무 많아서
실제로 JSON보다 느릴 것입니다.
왜냐하면 다시 말하지만, JSON은
제한된 문법이기 때문입니다.
JSON에는 그렇게 많은 타입이 없습니다.
실제로는 세 개 정도죠.
문자열, 숫자, 객체가 있고,
불린도 있습니다.
그래서 JSON을 파싱하는 것은 실제로 매우 효율적입니다.
그리고 이제는 더욱 빨라졌습니다.
메모리 위치와 레지스터를 재매핑하고 재사용하는 방법을
찾았기 때문입니다. 이는 직렬화하고
역직렬화하는 것들의
히든 클래스를 기반으로 합니다.
명확히 하자면,
이런 것들이 앱을 느리게 만드는
요소가 되기는 어렵습니다.
하지만 모든 구석을
최적화하고 싶은 타입이라면,
아마도 Chrome의 프로파일러를 통해
이미 이걸 배웠을 것입니다.
실제로 값을 할당하는 데 시간이 소요되는 것을 보고 있다면,
JSON으로 해보세요.
어떤 일이 일어나는지 보세요.
이 새로운 최적화로 돌아가서,
더 빠른 double-to-string 알고리즘입니다.
숫자를 문자열 표현으로 변환하는 것은
놀랍도록 복잡하고 성능에 중요한 작업입니다.
JSON stringify 작업의 일환으로,
우리는 핵심 double-to-string
알고리즘을 업그레이드하여
프로세스를 크게 가속화할
기회를 찾았습니다.
이제 오래된 Gris 3 알고리즘을 Dragon Box로 교체했습니다.
최단 길이 숫자-문자열 변환을 위해서요.
변환에 대한 이야기입니다. 숫자를
문자열로 바꾸는 방식이 이렇게
복잡해서 수많은 다른 구현들이
있었다는 게 정말 놀라워요.
그리고 드디어 새로운 방식으로
넘어갔네요. V8이 등장한 지 몇십 년이나
지난 후에 말이죠. 이건 Dragon Box의
참조 구현체인데, Dragon Box는
이론적으로
굉장한 연구 결과예요. 부동소수점
이진수를 십진수 문자열로 변환하는
방법에 관한 연구죠. 숫자를 문자열로
가능한 한 효율적으로 바꾸는 방법에 대해
완전한 연구 논문들이 있다는 게
정말 우스꽝스러워요. 그리고 이건
C++로 된 구현체인데, 이제 V8에
적용되어서 JSON stringify를 할 때
숫자가 문자열로 더 빠르게
변환될 거예요. Azam.js에 대해
알고 있어요.
저는 원조 웹 어셈블리 시절을
기억해요. 제가 당신보다 조금
나이가 많은 것 같아요. 그 일이
일어났을 때 저도 있었거든요. 정말
큰 순간이었죠. 우리는 asjs를 되살리고
싶지 않아요. WASM이 올바른
해답이죠. JavaScript에서 어셈블리가
작동하게 만들어서는 안 돼요. 웹에서
어셈블리처럼 작동하는 뭔가를
만들어야 해요. 당신은 20살이군요.
젠장, 당신이 얼마나 어린지 계속
잊게 돼요. 세상에. 당신의 지혜는
겨우 20년의 육신에 비해 너무
대단해요. 네, 너무 많이 알고 있어요.
이 최적화가 JSON stringify
프로파일링에 의해 추진되었지만,
새로운 Dragonbox 구현체는
V8 전체에서 number.prototype.toString
호출에 모두 도움이 됩니다. 그래서
숫자에 toString을 너무 자주
호출해서 앱이 느렸다면, 이제 덜
느려질 수 있어요. 좋네요. 이건
JSON 직렬화뿐만 아니라 숫자를
문자열로 변환하는 모든 코드가
이런 성능 향상을 볼 수 있다는
뜻이에요. 멋지네요. 그리고 더 많은
최적화가 있어요. 기본 임시 버퍼
최적화입니다. 모든 문자열 구축
작업에서 상당한 오버헤드의 원인은
메모리 관리 방식입니다. 이전에는
문자열 변환기가 C++ 힙의 단일
연속 버퍼에서 출력을 구축했어요.
단순하긴 하지만, 이 접근법에는
상당한 단점이 있었죠. 버퍼의
공간이 부족할 때마다 더 큰
버퍼를 할당하고 기존 내용을
모두 복사해야 했어요. 큰 JSON
객체의 경우, 재할당과 복사의
순환이 주요 성능 오버헤드를
만들었어요. 흥미롭네요. 다시 말하지만,
엔진이 계속 이런 일들을 쏟아내고
기회가 될 때마다 가비지 컬렉션을
해야 할 때 버퍼 오버플로는
큰 걱정거리예요. 핵심적인 통찰은
이 임시 버퍼를 연속적으로 유지하는
것이 실제로는 별다른 이점을
제공하지 않는다는 것이었어요. 최종
결과는 맨 마지막에만 단일 문자열로
조립되거든요. 이를 염두에 두고
기존 시스템을 분할된 버퍼로
대체했어요. 모든 것을 하나의 큰
증가하는 메모리 블록에 넣는 대신,
이제 V8 존 메모리에 할당된 작은
버퍼들 또는 세그먼트들의 목록을
새로운 세그먼트를 할당하고 거기에 계속 써나가면 됩니다.
이렇게 하면 비용이 많이 드는
복사 작업을 완전히 없앨 수 있습니다. 정말 멋지죠.
이런 깨달음들이 소프트웨어의
혁신적인 발전을 이끌어왔다는 걸
알면 놀라실 겁니다.
개발자들이 이런 식으로 생각하죠.
"아, 우리가 모든 걸 한 곳에 저장하고 있네.
끝까지 다 쓰지도 않는데 말이야.
그냥 여기저기 저장해두고
마지막에 합치면 어떨까?" 라고요.
Canada Honk가 또 좋은 지적을 했는데요.
정말 무서운 건 그 코드 중 어디든
한 줄이라도 실수가 있으면, 이론적으로는
샌드박스 탈출을 통해 JSON 파싱 중에
원격 코드 실행이 가능할 수도 있다는 겁니다.
정말 무서운 일이죠.
진짜 소름 끼칩니다. 이게 대충 짠 코드가
아니라는 게 정말 다행입니다.
재밌는 걸 한번 해보죠. 일부러
GPT-5한테 인터넷 액세스를 주지 않겠습니다.
검색 기능도 주지 않고요.
V8 엔진 내부의 JSON.stringify
성능을 개선할 수 있는 이론적인 방법들이
뭐가 있는지 물어볼 겁니다. V8의
소스 코드를 마음대로 수정할 수
있다고 가정하고 말이죠. 뭐라고 답하는지 보죠.
GPT-5가 뭐라고 할지 궁금하네요.
이런 최적화를 즉흥적으로
코딩한다면 어떨까요? 정말
궁금합니다.
그걸 기다리는 동안
한계점에 대해 얘기해 보죠. 새로운 고속 경로는
일반적이고 단순한 경우에 특화되어
속도를 달성합니다. 만약 직렬화되는
데이터가 이런 조건에
맞지 않으면 V8은 정확성을 보장하기 위해
범용 직렬화기로
돌아갑니다. 완전한 성능
향상을 얻으려면 JSON stringify 호출이
다음 조건들을 준수해야 합니다.
replacer나 space 인수가 없어야 합니다.
replacer 함수나 예쁘게 출력하기 위한
space, gap 인수는
범용 경로에서만 처리되는
기능들입니다. 재밌네요.
JSON stringify가 제공하는
멋진 기능들로 출력을
예쁘게 만들면 속도 최적화에서
배제된다는 거죠. 말이 됩니다.
순수한 데이터 객체와 배열이어야 합니다.
직렬화되는 객체들은
단순한 데이터 컨테이너여야 합니다. 즉 객체나
그 프로토타입들이 커스텀
toJSON 메서드를 가지면 안 됩니다.
만약 여러분만의 toJSON을 정의하고 있다면
우리보다 더 깊은 곳에 있는 거죠.
좋습니다. 멋져요. 즐기세요. 하지만 이런 메모리
할당 수준의 변경은
할 수 없습니다. 레지스터를 읽어서
커스텀 toJSON이 있을 때 뭘 해야 할지
알 수도 없고요. 말이 됩니다.
필요하지 않다면 커스텀 기능을
만들지 말아야 하는 이유가 하나 더 생겼네요.
고속 경로는 object.prototype이나
array.prototype 같은 표준 프로토타입이
커스텀 직렬화 로직을
가지지 않는다고 가정합니다.
그리고 이걸 보고 toJSON을
오버라이드할 수 있다는 걸 처음 알았다면
하지 마세요. 자신을 그런 고통에
빠뜨리지 마세요. 그럴 가치가 없습니다.
저도 그 함수로 끔찍한 일들을
해봤거든요. 하지 마세요. 저를 믿으세요.
위험이 기다리고 있어요. 여행자여, 매우 조심하세요. 객체에
인덱스 속성을 사용하지 마세요. 빠른
경로는 일반적인 문자열 기반
키를 가진 객체에 최적화되어 있습니다. 객체가
0, 1 등과 같은 배열 형태의 인덱스 속성을
포함하면, 더 느리고 일반적인 직렬화기가
처리하게 됩니다. 그리고 객체를 배열처럼
사용하지 말아야 하는 또 다른 이유죠. 객체는
객체로 두세요. 그리고 간단한 문자열
타입을 사용하세요. const 문자열과 같은 일부 내부 V8 문자열
표현은 직렬화되기 전에 평탄화를 위해
메모리 할당이
필요할 수 있습니다.
빠른 경로는 그런 할당을 유발할 수 있는
모든 작업을 피하고 간단한 순차적 문자열에서
가장 잘 작동합니다.
이는 웹 개발자로서 영향을 주기 어려운
부분이지만, 걱정하지 마세요. 대부분의
경우에는 그냥 잘 작동할 겁니다.
API 응답을 위한 데이터 직렬화나 구성
객체 캐싱과 같은 대부분의
사용 사례에서는 이러한 조건들이
자연스럽게 충족되어 개발자들이
성능 향상의 혜택을
자동으로 받을 수 있습니다.
여기서 T3의 GPT-5가
채팅으로 우리가 이론적으로 어떻게 최적화할 수 있는지
생각하고 있는 내용입니다. 이것들 중에
맞는 게 있는지 봅시다.
봐요. 실제로 몇 가지를
추측하고 있어요. 객체가 데이터 속성으로만
구성되어 있고, 안정적인 맵과
숨겨진 클래스를 가지고 있으며, 딕셔너리 모드가 없고
구멍을 의미하는 요소가
없다면요. ASCII가 아닌 문자를
스펙이 허용하는 대로 이스케이프하지 않거나 최대
처리량을 위해서요. 그리고 가드가 실패하면
기존의 일반적인 경로로 되돌리기. 같은
맵을 가진 객체에 대한 모양 기반
속성 계획. 열거 순서로 속성 필드의
안정적인 목록을 미리 컴파일하고
일반적인 속성 키 수집을 건너뛰고
필드를 직접 읽어서
직렬화하기.
이들이 이 계획으로 학습했나요? 이건
정말 정확해요. 요소들이
맵을 공유할 때 배열을 직렬화한다면
요소들 간에 속성 평면을 끌어올리고
재사용하기. 이것이 우리가 이야기한
배열 반복에 대한 내용이기도 해요. 이 모든 걸
꽤 잘 추측하고 있어요. 오, 봐요. 심지어
SIMD 벡터화된 ASCII 스캔도
포함했어요. 정말 웃기네요. 1바이트 문자열용과
2바이트 문자열용, 두 개의 계층 문자열
경로. 이것의 대부분을
맞췄어요.
결국 우리는 V8에서 문자열 직렬화의
성능 향상을 감으로
코딩할 수 있었을 거예요. 누가 생각했겠어요? 저는
생각 못했을 거예요. 절대로
우리가 감으로 이걸
코딩할 수 있다고 생각하지 못했을 거예요. 그리고 명확히 하자면, 우리는 감으로
이걸 코딩할 수는 없지만, 이런 식으로
대부분의 답을 얻을 수 있다는 건
정말 멋져요. 정말, 정말 멋져요. 그리고 이제
결과를 봅시다. 아름답네요. 왜 Pixel 9이
M1 Mac보다 문자열을 더 빨리
직렬화하나요? 상처받네요. 사실 결론을
읽어봐야겠어요. JSON Stringify를
처음부터 다시 생각해보고, 고수준
로직부터 핵심 메모리와
문자 처리 작업까지, 우리는
Jetstream 2 JSON stringify
inspector 벤치에서 측정된
2배 이상의 성능
향상을 달성했습니다. 아래 그림을 참조하세요. 이러한 최적화는
이제 버전 13.8부터 시작하는 V8에서
사용할 수 있습니다. 이는
Chrome 138입니다. 오,
점수네요, 시간이 아니라. 고마워요. 수정해줘서 고마워요,
Nean, 벤치마크가 시간 벤치마크가 아니라
점수 벤치마크거든요. 그래서 실제로는
막대가 높을수록 좋은 거예요. Mac 사용자는
계속 이기고 있어요.
받아들이겠습니다. 이건 정말 대단했어요. 이건
정말로 정말 좋은 글이었고
이 정보를 소화 가능하게 만들어줍니다. 그리고
이런 정보가 소화 가능해야 하는 건
아니거든요. 이걸 써준 Patrick에게
큰 박수를 보내고, 제가 이 정보를
잘못 얻지 않도록 도와준
Oliver에게도 박수를 보냅니다.
그는 가장 똑똑한 JS 엔진
전문가 중 한 명이에요. 그냥 진짜로요. 제가 JavaScript에 대해
이야기하는 많은 것들이
Oliver로부터 직접 얻은
정보입니다. 훌륭한 자료예요. 정말
사랑해요. JavaScript 엔진에 대한
그들의 완전한 광기를
추적하고 싶다면 분명히 팔로우해야 할
사람이에요.
아, JSON stringify처럼 빠르게
여기서 나갈 시간인 것 같네요.
정말 빠르게. 안녕!