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