- Trong engine V8, hiệu năng của hàm JSON.stringify đã được nâng lên hơn gấp đôi, giúp cải thiện tốc độ tuần tự hóa dữ liệu
- Đã bổ sung một đường tối ưu hóa dành cho đối tượng không có tác dụng phụ, nhờ đó có thể bỏ qua nhiều logic kiểm tra phòng thủ và đạt mức tăng tốc lớn với các đối tượng dữ liệu thông thường
- Ở phần xử lý chuỗi, V8 áp dụng nhiều kỹ thuật tối ưu hóa về phần cứng và bộ nhớ như phân biệt 1 byte/2 byte, tận dụng SIMD và thay đổi cấu trúc bộ đệm tạm
- Trong quá trình chuyển đổi số, thuật toán Grisu3 trước đây được thay bằng Dragonbox, mở ra khả năng chuyển đổi nhanh hơn cho cả các lệnh gọi
Number.toString() nói chung
- Với một số đối số và hình dạng dữ liệu nhất định, hệ thống sẽ quay lại đường tuần tự hóa thông thường, nhưng trong phần lớn tình huống phát triển web, bạn sẽ tự động được hưởng lợi từ tối ưu hóa này
Tổng quan
JSON.stringify là hàm cốt lõi dùng để chuyển dữ liệu thành chuỗi trong JavaScript
- Việc tăng hiệu năng cho hàm này cũng tác động tích cực đến những tác vụ rất quan trọng trên web như yêu cầu mạng hay lưu vào
localStorage
- Nhờ các cải tiến kỹ thuật mới nhất trong V8, tốc độ của tính năng này đã được cải thiện hơn gấp đôi, và bài viết trình bày chi tiết các hướng tối ưu chính
Đường Fast Path không có tác dụng phụ
- Trọng tâm của tối ưu hóa là áp dụng đường tuần tự hóa nhanh chỉ dùng được trong các tình huống không có tác dụng phụ (side-effect)
- Trong các trường hợp như vậy, thay vì dùng đệ quy, V8 duyệt đối tượng bằng cấu trúc lặp (iterative), nên không cần kiểm tra tràn ngăn xếp và cũng có thể thử tuần tự hóa các đối tượng sâu hơn
- Khi đối tượng dữ liệu đủ đơn giản, V8 sẽ dùng Fast Path này thay cho logic chung chậm hơn, bỏ qua nhiều kiểm tra và tăng tốc đáng kể
Xử lý nhiều kiểu biểu diễn chuỗi
- V8 lưu trữ chuỗi khác nhau tùy theo ký tự 1 byte/2 byte (ASCII/không phải ASCII); chỉ cần có một ký tự không phải ASCII thì toàn bộ sẽ được quản lý dưới dạng 2 byte
- Để tăng hiệu năng tuần tự hóa chuỗi, V8 biên dịch các phiên bản thuật toán riêng theo từng loại chuỗi
- Vì trong lúc xử lý vẫn phải kiểm tra kiểu thực thể chuỗi, nếu phát hiện chuỗi 2 byte thì bộ tuần tự hóa 2 byte phù hợp sẽ tiếp nhận trạng thái hiện tại
- Nhờ vậy, chi phí chuyển đổi đường xử lý theo kiểu mã hóa chuỗi gần như bằng không
- Kết quả cuối cùng được tạo bằng cách xây dựng riêng bộ đệm 1 byte và 2 byte rồi chỉ đơn giản hợp nhất chúng ở cuối
Tối ưu hóa tuần tự hóa chuỗi bằng SIMD
- Chuỗi JavaScript có thể chứa các ký tự cần escape khi tuần tự hóa JSON
- Với chuỗi dài, V8 dùng các lệnh phần cứng SIMD (như ARM64 Neon) để kiểm tra nhiều byte cùng lúc
- Với chuỗi ngắn, V8 dùng phương pháp SWAR, xử lý nhiều ký tự đồng thời bằng phép toán bit trong thanh ghi đa dụng
- Dù theo cách nào, trong đa số trường hợp toàn bộ chuỗi có thể được sao chép rất nhanh mà không cần biến đổi gì thêm
Bổ sung Express Lane (đường siêu tốc)
- Ngay cả trong Fast Path, V8 cũng bổ sung Express Lane để có thể tuần tự hóa chỉ bằng cách sao chép khóa mà không phải lặp lại các việc như kiểm tra thuộc tính
- Tận dụng cờ hidden class của đối tượng, nếu khóa không có Symbol, đều là enumerable và có thể được tuần tự hóa mà không cần escape, đối tượng sẽ được đánh dấu là
fast-json-iterable
- Khi tuần tự hóa đối tượng khác có cùng hidden class, V8 sẽ sao chép khóa ngay lập tức mà không cần kiểm tra thêm
- Kỹ thuật này cũng được áp dụng trong
JSON.parse để tăng tốc so sánh khóa
Thuật toán double-to-string nhanh hơn
- Quá trình chuyển số thành chuỗi cũng diễn ra rất thường xuyên và có độ phức tạp cao
- Việc thay thế thuật toán Grisu3 cũ bằng Dragonbox cũng giúp tăng hiệu năng cho toàn bộ các lệnh gọi
Number.prototype.toString()
Tối ưu hóa cấu trúc bộ đệm tạm
- Trước đây khi xây dựng chuỗi, V8 dùng một bộ đệm liên tục duy nhất, nên mỗi khi thiếu chỗ lại phát sinh chi phí sao chép toàn bộ dữ liệu
- Cách mới dùng cấu trúc bộ đệm phân đoạn (segmented), nối nhiều bộ đệm nhỏ theo nhu cầu
- Nhờ đó, khi hết chỗ, hệ thống chỉ cần cấp phát bộ đệm mới mà không phải sao chép toàn bộ
Giới hạn
- Fast Path chỉ hoạt động cho việc tuần tự hóa dữ liệu đơn giản
- Nếu không thỏa các điều kiện dưới đây thì hệ thống sẽ dùng đường xử lý thông thường
- Không được dùng đối số replacer hoặc space (không hỗ trợ pretty-print hay biến đổi)
- Phải là đối tượng đơn giản, không có phương thức tùy biến toJSON
- Nếu có thuộc tính dựa trên chỉ số, hệ thống sẽ chuyển sang đường chậm
- Chuỗi đặc biệt như ConsString không được xử lý theo đường này
- Trong hầu hết các trường hợp phổ biến như tuần tự hóa dữ liệu, tạo phản hồi API hay cache cấu hình, tối ưu hóa này sẽ tự động được áp dụng
Kết luận
JSON.stringify đã được tái cấu trúc cách tiếp cận trên toàn bộ bề mặt, từ thiết kế nền tảng đến xử lý bộ nhớ và xử lý ký tự, qua đó đạt mức tăng tốc hơn 2 lần theo benchmark JetStream2
- Các cải tiến này có thể trải nghiệm ngay từ V8 phiên bản 13.8 (Chrome 138) trở lên
1 bình luận
Ý kiến Hacker News
Cảm thấy việc mã hóa JSON là một nút thắt lớn trong giao tiếp giữa các tiến trình trên NodeJS
JSON.stringifylà một trong những yếu tố lớn nhất chặn hiệu năng của dịch vụ NodeTôi rất ngạc nhiên về việc hiệu năng serialize số dấu phẩy động đã cải thiện nhiều đến mức nào trong khoảng hơn 10 năm gần đây
Có nói rằng nếu
JSON.stringifycó đối số replacer hoặc space thì fast path sẽ không được áp dụngJSON.stringify(data, null, 0)có còn vào fast path được không, hay đối số phải làundefinedmới được?Thuật toán escaping SWAR[1] rất giống với thứ tôi từng triển khai trong Folly JSON[2]
Tôi không nghi ngờ giá trị của công việc này, nhưng muốn biết rõ hơn các vấn đề hay dữ liệu cụ thể trong hệ sinh thái V8 nơi
JSON.stringifythực sự chi phối runtimeTôi nghĩ hiệu năng của v8 chưa được khen ngợi đủ mức, JS dạo này đã nhanh lên khủng khiếp
Tò mò không biết việc này xuất sắc đến mức nào nếu so với các hệ sinh thái khác
“No indexed properties on objects” — có vẻ fast path chỉ tối ưu cho object thông thường với key dạng chuỗi, còn nếu có thuộc tính chỉ mục kiểu array-like thì sẽ quay về slow path
Tôi thích cách dùng segmented buffer, ngày xưa phải tự dùng mấy thư viện userland như fast-json-stringify để làm trò rope, giờ có native thì tốt hơn nhiều
.toJSON()tùy biến) có thường gặp không, và trong các trường hợp đó có rơi thẳng sang đường chậm hay khôngV8 rất xuất sắc, nhưng có vẻ vì bản thân JS mà hiệu năng vẫn kém hơn LuaJIT hay JVM