Hướng dẫn nodejs v8 engine - công cụ nodejs v8
V8 là một công cụ JavaScript mã nguồn mở được phát triển bởi dự án Chromium cho trình duyệt web Google Chrome. Nó được viết bằng C ++. Ngày nay, nó được sử dụng trong nhiều dự án như Couchbase, MongoDB và Node.js. Các bài viết liên quan: V8 trong Node.jsVí dụ về Node.js v8.getHeapSt Statistics ()v8.getHeapSt Statistics () và v8.getHeapSpaceSt Statistics () . Ví dụ về Node.js v8.getHeapSpaceSt Statistics ()request (‘v8’) . const v8 = request ( 'v8' ); Ví dụ về Node.js v8.getHeapSt Statistics ()Ví dụ về Node.js v8.getHeapSpaceSt Statistics () Giới hạn bộ nhớ của V8 trong Node.js const v8 = request ( 'v8' ); console.log (v8.getHeapStosystem ()); Ví dụ về Node.js v8.getHeapSpaceSt Statistics ()Giới hạn bộ nhớ của V8 trong Node.js Mô-đun Node.js V8 đại diện cho các giao diện và sự kiện cụ thể cho phiên bản V8. Nó cung cấp các phương thức để lấy thông tin về bộ nhớ heap thông qua các phương thức v8.getHeapSt Statistics () và v8.getHeapSpaceSt Statistics () . const v8 = request ( 'v8' ); console.log (v8.getHeapSpaceSt Statistics ()); Giới hạn bộ nhớ của V8 trong Node.jsMô-đun Node.js V8 đại diện cho các giao diện và sự kiện cụ thể cho phiên bản V8. Nó cung cấp các phương thức để lấy thông tin về bộ nhớ heap thông qua các phương thức v8.getHeapSt Statistics () và v8.getHeapSpaceSt Statistics () . Bài này sẽ đi sâu vào các phần V8 JavaScript engine của Google. Tôi sẽ cung cấp một số mẹo nhỏ về cách viết code JavaScript tốt hơn – đạt hiệu suất hơn mà nhóm phát triển của chúng tôi tại SessionStack thực hiện khi xây dựng sản phẩm. Tổng quanMột JavaScript engine là chương trình hoặc là interpreter (trình thông dịch) thực thi code JavaScript. Một JavaScript engine có thể được thực hiện như một trình thông dịch tiêu chuẩn, hoặc compiler (trình biên dịch) phù hợp để biên dịch JavaScript thành bytecode trong một số hình thức.JavaScript engine là chương trình hoặc là interpreter (trình thông dịch) thực thi code JavaScript. Một JavaScript engine có thể được thực hiện như một trình thông dịch tiêu chuẩn, hoặc compiler (trình biên dịch) phù hợp để biên dịch JavaScript thành bytecode trong một số hình thức. Đây là danh sách các dự án phổ biến đang triển khai JavaScript engine:
Tại sao V8 Engine được tạo ra?V8 Engine được xây dựng bởi Google là mã nguồn mở và được viết bằng C ++. Công cụ này được sử dụng trong Google Chrome. Tuy nhiên, khác với phần còn lại của engine, V8 cũng được sử dụng phổ biến cho Node.js. V8 lần đầu tiên được thiết kế để tăng hiệu suất việc thực hiện JavaScript bên trong các trình duyệt web. Để tối ưu hóa tốc độ, V8 chuyển code JavaScript thành machine code thay vì sử dụng một interpreter. Nó biên dịch code JavaScript vào machine code khi thi hành bằng cách sử dụng trình biên dịch JIT (Just-In-Time) như rất nhiều công cụ JavaScript hiện đại như SpiderMonkey hay Rhino (Mozilla). Sự khác biệt chính ở đây là V8 không tạo ra code bytecode hoặc bất kỳ code trung gian nào. V8 từng có 2 trình biên dịchTrước khi phiên bản 5.9 của V8 xuất hiện (phát hành đầu năm nay), engine đã sử dụng hai trình biên dịch:
V8 Engine cũng sử dụng một số threads internally:
Khi thực hiện code JavaScript lần đầu tiên, V8 thúc đẩy full-codegen trực tiếp chuyển code Javascript thành machine code mà không cần chuyển đổi. Điều này cho phép nó bắt đầu thực hiện machine code rất nhanh. Lưu ý rằng V8 không sử dụng bytecode trung gian bằng cách này loại bỏ sự cần thiết của interpreterfull-codegen trực tiếp chuyển code Javascript thành machine code mà không cần chuyển đổi. Điều này cho phép nó bắt đầu thực hiện machine code rất nhanh. Lưu ý rằng V8 không sử dụng bytecode trung gian bằng cách này loại bỏ sự cần thiết của interpreter Khi code của bạn đã chạy một thời gian, luồng profiler đã tập hợp đủ dữ liệu để cho biết phương pháp nào nên được tối ưu hóa. Tiếp theo, tối ưu hóa Crankshaft được thực hiện ở 1 luồng khác. Mục đích để dịch cú pháp JavaScript thành single-assignment (SSA) được gọi là Hydrogen và cố gắng tối ưu hóa đồ thị Hydro. Hầu hết các tối ưu hóa được thực hiện ở mức này.Crankshaft được thực hiện ở 1 luồng khác. Mục đích để dịch cú pháp JavaScript thành single-assignment (SSA) được gọi là Hydrogen và cố gắng tối ưu hóa đồ thị Hydro. Hầu hết các tối ưu hóa được thực hiện ở mức này. InliningTối ưu hóa đầu tiên là inlining càng nhiều code càng tốt. Inlining là quá trình thay thế một call site (dòng code nơi chức năng được gọi) với phần thân của hàm được gọi. Bước này cho phép tối ưu hóa sau có ý nghĩa hơn. Hidden classJavaScript là một ngôn ngữ dựa trên nguyên mẫu: không có các class và các object được tạo ra bằng cách sử dụng quá trình nhân bản. JavaScript cũng là một ngôn ngữ lập trình động, có nghĩa là các thuộc tính có thể dễ dàng thêm hoặc xoá khỏi một object sau khi nó được khởi tạo. Hầu hết các trình biên dịch JavaScript sử dụng các cấu trúc giống như từ điển (dựa trên chức năng hash) để lưu trữ vị trí các giá trị thuộc tính đối tượng trong bộ nhớ. Cấu trúc này làm cho việc lấy giá trị của một thuộc tính trong JavaScript phức tạp hơn nhiều so với các ngôn ngữ lập trình non-dynamic như: Java hay C #. Trong Java, tất cả các thuộc tính của đối tượng được xác định bởi một bố cục cố định trước khi biên dịch và không thể tự động thêm hoặc xoá khi chạy. Kết quả là các giá trị thuộc tính (hoặc các trỏ tới các thuộc tính đó) có thể được lưu trữ dưới dạng một bộ nhớ đệm bổ sung liên tục. Độ dài của offset có thể dễ dàng được xác định dựa trên loại thuộc tính, trong khi điều này là không thể trong JavaScript khi mà chương trình đang chạy. Vì sử dụng dictionaries để tìm vị trí của các thuộc tính đối tượng trong bộ nhớ là rất không hiệu quả, V8 sử dụng một phương pháp khác thay thế: hidden classes. Các hidden classes có chức năng tương tự như object layout, ( class) được sử dụng như trong Java ngoại trừ các class được tạo ra trong thời gian chạy. Bây giờ, chúng ta hãy xem những gì nó thực sự như thế nào:hidden classes. Các hidden classes có chức năng tương tự như object layout, ( class) được sử dụng như trong Java ngoại trừ các class được tạo ra trong thời gian chạy. Bây giờ, chúng ta hãy xem những gì nó thực sự như thế nào: function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(1, 2); Khi “invocation Point (1, 2)” mới xảy ra, V8 sẽ tạo ra một hidden classes gọi là “C0”. Không có thuộc tính nào được xác định cho Point, vì vậy “C0” trống. Khi câu lệnh đầu tiên “this.x = x” được thực hiện (bên trong hàm “Point”), V8 sẽ tạo ra một hidden classes thứ nhì gọi là “C1” dựa trên “C0”. “C1” mô tả vị trí trong bộ nhớ (tương đối so với con trỏ đối tượng) nơi có thể tìm thấy thuộc tính x. Trong trường hợp này, “x” được lưu trữ tại offset 0, có nghĩa là khi xem một đối tượng điểm trong bộ nhớ như một bộ đệm liên tục, lần bù đầu tiên sẽ tương ứng với thuộc tính “x”. V8 sẽ cập nhật “C0” với một “class transition” trong đó nếu một thuộc tính “x” được thêm vào một đối tượng điểm, nên hidden classes chuyển từ “C0” thành “C1”. Hidden classes cho đối tượng bên dưới bây giờ là “C1”. Quá trình này được lặp lại khi câu lệnh “this.y = y” được thực hiện (một lần nữa, bên trong chức năng Point, sau câu lệnh “this.x = x”). Một hidden classes mới được gọi là “C2” được tạo ra, sự chuyển đổi class được thêm vào “C1” cho biết nếu một thuộc tính “y” được thêm vào một đối tượng Point (đã chứa thuộc tính “x”), thì hidden classes sẽ thay đổi thành “C2”, và hidden classes của đối tượng được cập nhật thành “C2”. Các chuyển tiếp hidden classes phụ thuộc vào thứ tự thuộc tính được thêm vào một đối tượng. Hãy xem đoạn code dưới đây: function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(1, 2); p1.a = 5; p1.b = 6; var p2 = new Point(3, 4); p2.b = 7; p2.a = 8; Bây giờ, bạn sẽ giả định rằng đối với cả p1 và p2 thì các hidden classes và transitions sẽ được sử dụng. Vâng, không thực sự. Đối với “p1”, đầu tiên thuộc tính “a” sẽ được thêm vào và thuộc tính “b”. Tuy nhiên, đối với “p2”, đầu tiên “b” đang được chỉ định, tiếp theo là “a”. Do đó, “p1” và “p2” kết thúc với các hidden classes khác nhau do kết quả của các transitions khác nhau. Trong những trường hợp này, tốt hơn là nên khởi tạo các thuộc tính động theo cùng thứ tự sao cho các hidden classes có thể được sử dụng lại. Inline cachingV8 tận dụng lợi thế kỹ thuật khác để tối ưu hóa các ngôn ngữ đánh máy được gọi là inline caching. Inline caching dựa trên quan sát rằng các repeated calls lặp lại với cùng một phương pháp có xu hướng xảy ra trên cùng một loại đối tượng. Một giải thích sâu về Inline caching có thể được tìm thấy ở đây. Chúng ta sẽ tiếp xúc với khái niệm chung về inline caching (trong trường hợp bạn không có thời gian để tìm hiểu sâu hơn). Làm thế nào nó hoạt động? V8 duy trì một bộ nhớ cache của loại đối tượng đã được truyền như là một tham số trong các calls gần nhất và sử dụng thông tin này để tạo ra một giả định về loại đối tượng sẽ được truyền như một tham số trong tương lai. Nếu V8 có thể tạo ra một giả định tốt về loại đối tượng nó có thể bỏ qua quá trình tìm ra cách truy cập các thuộc tính của đối tượng, và thay vào đó, sử dụng các thông tin được lưu trữ từ các tra cứu trước đó tới đối tượng trong các hidden classes. Vậy các khái niệm về hidden classes và inline caching có liên quan như thế nào? Bất cứ khi nào một phương thức được gọi trên một đối tượng cụ thể, V8 phải thực hiện một tra cứu đến hidden classes của đối tượng đó để xác định độ offset cho việc truy cập một thuộc tính cụ thể. Sau hai call thành công của cùng phương pháp đến cùng một hidden classes, V8 bỏ qua việc tra cứu hidden classes và đơn giản chỉ thêm offset vào chính con trỏ của đối tượng. Đối với tất cả các call trong tương lai của phương pháp đó, V8 giả định rằng hidden classes không thay đổi và nhảy trực tiếp vào địa chỉ bộ nhớ cho một thuộc tính cụ thể bằng cách sử dụng các hiệu số được lưu trữ từ các tra cứu trước đó. Điều này làm tăng tốc độ thực hiện. Đây cũng là lý do tại sao Inline caching quan trọng đến nỗi các đối tượng cùng loại chia sẻ các hidden classes. Nếu bạn tạo hai đối tượng cùng loại và với các hidden classes khác nhau (như chúng ta đã làm trong ví dụ trước đó), V8 sẽ không thể sử dụng bộ nhớ đệm nội tuyến bởi vì mặc dù hai đối tượng cùng loại, các lớp ẩn tương ứng của chúng Gán các hiệu số khác nhau cho thuộc tính của chúng. Biên soạn machine codeMột khi biểu đồ Hydrogen được tối ưu, Crankshaft giảm nó xuống mức thấp hơn là Lithium. Hầu hết việc thực hiện Lithium là kiến trúc cụ thể. Đăng ký phân bổ sẽ xảy ra ở giai đoạn này. Cuối cùng, Lithium được biên dịch thành machine code. Sau đó, cái được gọi là OSR: on-stack thay thế. Trước khi chúng tôi bắt đầu biên dịch và tối ưu hoá một phương pháp chạy lâu dài, chúng tôi có thể chạy nó. V8 sẽ không quên những gì nó đã thực hiện và chỉ thực sự bắt đầu lại với phiên bản được tối ưu hóa. Nó sẽ chuyển đổi tất cả những gì chúng ta có (stack, register) để có thể chuyển sang phiên bản được tối ưu hóa ở giữa thực thi. Đây là một nhiệm vụ rất phức tạp, lưu ý rằng trong số các tối ưu hóa khác, V8 đã inlined code ban đầu V8 không phải là engine duy nhất có khả năng làm việc đó. Có những biện pháp bảo vệ được gọi là khước từ để thực hiện việc chuyển đổi ngược lại và quay trở lại code không được tối ưu hoá trong trường hợp giả định rằng engine đã thực hiện không còn đúng nữa. Tổng hợp file rácĐể tổng hợp file rác, V8 sử dụng cách tiếp cận thế hệ tiếp theo bằng việc đánh dấu và quét để làm sạch thế hệ cũ. Giai đoạn đánh dấu là để kết thúc việc thực hiện JavaScript. Để kiểm soát chi phí của GC và làm cho việc thực hiện ổn định hơn, V8 sử dụng đánh dấu thay vì đi bộ toàn bộ đống, cố gắng đánh dấu mọi đối tượng có thể, nó chỉ đánh dầu một phần của hệ thống, sau đó khôi phục lại việc thực hiện bình thường. GC tiếp theo sẽ tiếp tục đi đến các heap trước khi dừng lại. Điều này cho phép tạm dừng rất ngắn so với quá trình thực hiện bình thường. Như đã đề cập ở trên, giai đoạn quét được xử lý bởi các luồng riêng biệt. Ignition và TurboFanVới việc phát hành V8 5.9 sớm hơn vào năm 2017, một đường dẫn thực hiện mới đã được giới thiệu. Đường dẫn mới này đạt được những cải tiến về hiệu năng lớn hơn và tiết kiệm đáng kể bộ nhớ trong các ứng dụng JavaScript thực. Các đường dẫn thực hiện mới được xây dựng trên đầu trang của Ignition, V8’s interpreter, và TurboFan, trình biên dịch tối ưu hóa mới nhất của V8. Bạn có thể kiểm tra bài đăng blog từ nhóm V8 về chủ đề ở đây. Kể từ phiên bản 5.9 của V8 ra đời, phiên bản Vende và Crankshaft (công nghệ đã phục vụ V8 từ năm 2010) đã không còn được sử dụng bởi V8 vì việc thực hiện JavaScript vì V8 đã phải vật lộn để theo kịp các tính năng ngôn ngữ JavaScript mới và cac tối ưu hóa cần thiết cho các tính năng này. Điều này có nghĩa là V8 tổng thể sẽ có kiến trúc đơn giản hơn và có thể duy trì được nhiều kiến trúc hơn nữa. Những cải tiến này mới chỉ là sự khởi đầu. Ignition và TurboFan mở đường cho những tối ưu hóa tiếp theo sẽ làm tăng hiệu suất của JavaScript và giảm dấu chân của V8 trong cả Chrome và Node.js trong những năm tới. Cuối cùng, đây là một số mẹo và thủ thuật về cách viết tốt nhất, tối ưu hóa, JavaScript tốt hơn. Nó được bắt nguồn từ nội dung ở trên, tuy nhiên, đây là một bản tóm tắt ngắn gọn hơn. Làm thế nào để viết JavaScript tối ưu hóa
Từ thực tiễn công việc của chúng tôi tại SessionStack thực hiện theo những cách này để viết code JavaScript tối ưu hóa cao. Lý do là khi bạn tích hợp SessionStack vào ứng dụng web của mình, nó bắt đầu ghi lại mọi thứ: thay đổi DOM , UI, JavaScript exceptions, stack traces, failed network requests, và debug messages. Với SessionStack, bạn có thể reply các vấn đề trong ứng dụng web của mình dưới dạng video và xem mọi thứ đã xảy ra với người dùng của bạn. Và tất cả điều này xảy ra mà không ảnh hưởng đến hiệu suất ứng dụng web của bạn. Có một kế hoạch cho phép bạn bắt đầu miễn phí. Nguồn: Techtalk via Sessionstack |