Quản lý bộ nhớ hiệu quả ở quy mô Gmail

John McCutchan
John McCutchan
Loreena Lee
Loreena Lee

Giới thiệu

Mặc dù JavaScript sử dụng tính năng thu thập rác để quản lý bộ nhớ tự động, nhưng nó không thay thế cho việc quản lý bộ nhớ hiệu quả trong các ứng dụng. Các ứng dụng JavaScript cũng gặp phải những vấn đề liên quan đến bộ nhớ như ứng dụng gốc, chẳng hạn như rò rỉ và cồng kềnh bộ nhớ. Tuy nhiên, các ứng dụng này cũng phải xử lý các vấn đề tạm dừng thu thập rác. Các ứng dụng quy mô lớn như Gmail gặp phải các sự cố tương tự đối với các ứng dụng nhỏ hơn của bạn. Đọc tiếp để tìm hiểu cách nhóm Gmail sử dụng Công cụ của Chrome cho nhà phát triển để xác định, tách biệt và khắc phục các vấn đề về bộ nhớ của họ.

Phiên hội thảo Google I/O 2013

Chúng tôi đã trình bày tài liệu này tại Google I/O 2013. Hãy xem video dưới đây:

Gmail, chúng tôi gặp sự cố...

Nhóm Gmail đã gặp phải một sự cố nghiêm trọng. Những câu chuyện về việc các thẻ Gmail ngốn nhiều gigabyte bộ nhớ trên máy tính xách tay và máy tính để bàn bị hạn chế tài nguyên ngày càng được lắng nghe nhiều hơn, thường đi kèm với kết luận là việc hạ cấp toàn bộ trình duyệt. Câu chuyện về việc CPU bị ghim ở mức 100%, ứng dụng không phản hồi và các thẻ buồn trên Chrome (" xử lý xong rồi, Jim."). Nhóm vẫn chưa biết cách bắt đầu chẩn đoán vấn đề chứ chưa nói đến việc khắc phục vấn đề. Họ không biết mức độ phổ biến của vấn đề và các công cụ sẵn có không mở rộng được cho các ứng dụng lớn. Nhóm đã hợp tác với các nhóm Chrome và họ cùng nhau phát triển các kỹ thuật mới nhằm phân loại các vấn đề về bộ nhớ, cải thiện các công cụ hiện có và cho phép thu thập dữ liệu bộ nhớ từ hiện trường. Tuy nhiên, trước khi đến với các công cụ, hãy cùng tìm hiểu các khái niệm cơ bản về việc quản lý bộ nhớ JavaScript.

Kiến thức cơ bản về quản lý bộ nhớ

Trước khi có thể quản lý hiệu quả bộ nhớ trong JavaScript, bạn phải nắm được các kiến thức cơ bản. Phần này sẽ đề cập đến các loại nguyên hàm, biểu đồ đối tượng và đưa ra định nghĩa về tình trạng tràn bộ nhớ nói chung và rò rỉ bộ nhớ trong JavaScript. Bộ nhớ trong JavaScript có thể được khái niệm dưới dạng biểu đồ. Do đó, Lý thuyết biểu đồ này đóng một vai trò trong hoạt động quản lý bộ nhớ JavaScript và Trình phân tích bộ nhớ khối xếp.

Loại nguyên gốc

JavaScript có ba loại chính:

  1. Số (ví dụ: 4, 3.14159)
  2. Boolean (đúng hoặc sai)
  3. Chuỗi ("Hello World")

Các kiểu dữ liệu nguyên gốc này không thể tham chiếu đến bất kỳ giá trị nào khác. Trong biểu đồ đối tượng, các giá trị này luôn là các nút lá hoặc các nút kết thúc, có nghĩa là chúng không bao giờ có cạnh hướng đi.

Chỉ có một loại vùng chứa: Đối tượng. Trong JavaScript, Đối tượng là một mảng kết hợp. Đối tượng không trống là một nút bên trong có các cạnh đi ra đến các giá trị khác (nút).

Tìm hiểu về mảng

Mảng trong JavaScript thực sự là một Đối tượng có các khoá số. Đây là cách đơn giản hoá vì môi trường thời gian chạy JavaScript sẽ tối ưu hoá các Đối tượng giống như Array và biểu thị chúng dưới dạng mảng.

Thuật ngữ

  1. Giá trị – Một thực thể của loại nguyên gốc, Đối tượng, Mảng, v.v.
  2. Biến – Tên tham chiếu đến một giá trị.
  3. Thuộc tính – Tên trong một Đối tượng tham chiếu đến một giá trị.

Biểu đồ đối tượng

Tất cả các giá trị trong JavaScript đều là một phần của biểu đồ đối tượng. Biểu đồ bắt đầu bằng gốc, ví dụ: đối tượng cửa sổ. Bạn không kiểm soát được thời gian tồn tại của các gốc GC không kiểm soát được vì các gốc này do trình duyệt tạo ra và bị huỷ bỏ khi trang bị huỷ tải. Biến toàn cục thực ra là các thuộc tính trên cửa sổ.

Biểu đồ đối tượng

Khi nào một giá trị trở thành rác?

Một giá trị sẽ trở thành rác khi không có đường dẫn từ gốc đến giá trị đó. Nói cách khác, bắt đầu từ gốc và tìm kiếm kỹ lưỡng tất cả các thuộc tính và biến của Đối tượng đang hoạt động trong khung ngăn xếp, một giá trị không thể đạt được, giá trị đó đã trở thành rác.

Biểu đồ rác

Rò rỉ bộ nhớ trong JavaScript là gì?

Tình trạng rò rỉ bộ nhớ trong JavaScript thường xảy ra nhất khi có các nút DOM không thể truy cập được từ cây DOM của trang, nhưng vẫn được một đối tượng JavaScript tham chiếu. Mặc dù các trình duyệt hiện đại đang ngày càng khó tạo ra các thông tin rò rỉ, nhưng việc này vẫn dễ dàng hơn mọi người nghĩ. Giả sử bạn nối thêm một phần tử vào cây DOM như sau:

email.message = document.createElement("div");
displayList.appendChild(email.message);

Sau đó, bạn sẽ xoá phần tử đó khỏi danh sách hiển thị:

displayList.removeAllChildren();

Khi email tồn tại, thành phần DOM được tham chiếu theo thông báo sẽ không bị xoá, mặc dù phần tử này hiện đã được tách khỏi cây DOM của trang.

Bloat là gì?

Trang của bạn sẽ lớn hơn khi bạn đang sử dụng nhiều bộ nhớ hơn mức cần thiết để đạt tốc độ trang tối ưu. Nói một cách trực tiếp, việc rò rỉ bộ nhớ cũng gây ra tình trạng cồng kềnh nhưng điều đó không phải là do nguyên nhân. Bộ nhớ đệm của ứng dụng không có bất kỳ giới hạn kích thước nào là một nguyên nhân phổ biến của tình trạng đầy bộ nhớ. Ngoài ra, trang của bạn có thể bị lấp đầy bởi dữ liệu máy chủ lưu trữ, ví dụ như dữ liệu pixel được tải từ hình ảnh.

Thu gom rác là gì?

Thu gom rác là cách bộ nhớ được thu hồi trong JavaScript. Trình duyệt sẽ quyết định thời điểm điều này xảy ra. Trong quá trình thu thập, tất cả quá trình thực thi tập lệnh trên trang của bạn sẽ bị tạm ngưng, còn các giá trị đang hoạt động được phát hiện qua quá trình truyền tải biểu đồ đối tượng, bắt đầu từ gốc GC. Tất cả giá trị không có thể tiếp cận đều được phân loại là rác. Trình quản lý bộ nhớ sẽ thu hồi bộ nhớ cho các giá trị rác.

Thông tin chi tiết về thiết bị thu gom rác V8

Để giúp hiểu thêm về cách thu gom rác, hãy tìm hiểu chi tiết về trình thu gom rác V8. V8 sử dụng một bộ thu thập thế hệ. Bộ nhớ được chia thành hai thế hệ: người trẻ và thế hệ cũ. Việc phân bổ và thu thập hình ảnh trong thế hệ trẻ diễn ra nhanh chóng và thường xuyên. Việc phân bổ và thu thập trong thế hệ cũ chậm hơn và ít thường xuyên hơn.

Bộ sưu tập thế hệ

V8 sử dụng một bộ sưu tập hai thế hệ. Thời gian tồn tại của một giá trị được định nghĩa là số byte được phân bổ kể từ khi giá trị đó được phân bổ. Trong thực tế, tuổi của một giá trị thường được ước chừng bằng số lượng bộ sưu tập thế hệ trẻ mà giá trị đó còn tồn tại. Sau khi một giá trị đủ cũ, giá trị đó sẽ được giữ nguyên vào thế hệ cũ.

Trong thực tế, các giá trị mới được phân bổ không tồn tại lâu dài. Một nghiên cứu về các chương trình của SmallTalk cho thấy rằng chỉ có 7% giá trị được tồn tại sau khi bộ sưu tập thế hệ trẻ được sưu tầm. Các nghiên cứu tương tự trong thời gian chạy cho thấy trung bình từ 90% đến 70% giá trị mới được phân bổ không bao giờ được giữ lại cho thế hệ cũ.

Thế hệ trẻ

Vùng nhớ khối xếp thế hệ trẻ trong V8 được chia thành hai không gian, có tên từ và đến. Bộ nhớ được phân bổ từ không gian này. Quá trình phân bổ diễn ra rất nhanh chóng, cho đến khi không gian đầy đủ, cũng là thời điểm một bộ sưu tập thế hệ trẻ được kích hoạt. Bộ sưu tập thế hệ trẻ trước tiên hoán đổi dữ liệu từ và sang không gian, dữ liệu cũ sang không gian (bây giờ là từ không gian) được quét và tất cả giá trị trực tiếp được sao chép vào không gian hoặc được duy trì vào không gian cũ. Một bộ sưu tập video thế hệ trẻ điển hình sẽ có thời lượng 10 mili giây (mili giây).

Theo trực quan, bạn nên hiểu rằng mỗi mức phân bổ mà ứng dụng của bạn thực hiện sẽ khiến bạn gần như sử dụng hết không gian hơn và dẫn đến việc tạm dừng GC. Lưu ý dành cho nhà phát triển trò chơi: để đảm bảo thời gian kết xuất khung hình 16 mili giây (cần phải có để đạt được 60 khung hình/giây), ứng dụng của bạn không được phân bổ xung đột, vì một bộ sưu tập thế hệ trẻ sẽ chiếm phần lớn thời gian kết xuất khung hình.

Vùng nhớ khối xếp thế hệ trẻ

Thế hệ cũ

Vùng nhớ khối xếp thế hệ cũ trong V8 sử dụng thuật toán đánh dấu nhỏ gọn để thu thập. Việc phân bổ thế hệ cũ diễn ra bất cứ khi nào một giá trị được duy trì từ thế hệ trẻ sang thế hệ cũ. Bất cứ khi nào một bộ sưu tập thế hệ cũ xuất hiện, một bộ sưu tập thế hệ trẻ cũng được thực hiện. Ứng dụng của bạn sẽ tạm dừng theo thứ tự giây. Trên thực tế, điều này có thể chấp nhận được vì các bộ sưu tập thế hệ cũ không thường xuyên.

Tóm tắt GC V8

Tính năng quản lý bộ nhớ tự động bằng tính năng thu gom rác là một công cụ tuyệt vời giúp nhà phát triển tăng năng suất. Tuy nhiên, mỗi lần phân bổ một giá trị, bạn lại tiến gần hơn đến thời điểm tạm dừng thu gom rác. Việc tạm dừng thu gom rác có thể ảnh hưởng xấu đến cảm giác của ứng dụng do hiện tượng giật. Bây giờ, bạn đã hiểu cách JavaScript quản lý bộ nhớ, bạn có thể đưa ra lựa chọn đúng cho ứng dụng của mình.

Sửa lỗi Gmail

Trong năm qua, nhiều tính năng và bản sửa lỗi đã được đưa vào Công cụ của Chrome cho nhà phát triển, giúp công cụ này trở nên mạnh mẽ hơn bao giờ hết. Ngoài ra, chính trình duyệt đã thực hiện một thay đổi quan trọng đối với API Performance.memory giúp Gmail và bất kỳ ứng dụng nào khác có thể thu thập số liệu thống kê về bộ nhớ từ trường này. Với những công cụ tuyệt vời này, những việc tưởng như bất khả thi đã sớm trở thành một trò chơi thú vị giúp truy tìm thủ phạm.

Công cụ và kỹ thuật

API Dữ liệu trường và Performance.memory

Kể từ Chrome 22, API performance.memory được bật theo mặc định. Đối với các ứng dụng hoạt động lâu dài như Gmail, dữ liệu từ người dùng thực là vô giá. Thông tin này cho phép chúng tôi phân biệt giữa người dùng thành thạo — những người dành từ 8 đến 16 giờ mỗi ngày trên Gmail, nhận hàng trăm thư mỗi ngày — với những người dùng thông thường dành vài phút mỗi ngày trong Gmail — nhận khoảng vài thư mỗi tuần.

API này trả về 3 phần dữ liệu:

  1. jsHeapSizeLimit – Dung lượng bộ nhớ (tính bằng byte) mà vùng nhớ khối xếp JavaScript giới hạn.
  2. totalJSHeapSize – Dung lượng bộ nhớ (tính bằng byte) mà vùng nhớ khối xếp JavaScript đã phân bổ bao gồm cả dung lượng trống.
  3. usedJSHeapSize – Dung lượng bộ nhớ (tính bằng byte) hiện đang được sử dụng.

Một điều cần lưu ý là API sẽ trả về các giá trị bộ nhớ cho toàn bộ quá trình Chrome. Mặc dù đó không phải là chế độ mặc định, nhưng trong một số trường hợp, Chrome có thể mở nhiều thẻ trong cùng một quy trình kết xuất đồ hoạ. Điều này có nghĩa là các giá trị được trả về bởi Performance.memory có thể chứa dấu vết bộ nhớ của các thẻ trình duyệt khác ngoài thẻ chứa ứng dụng của bạn.

Bộ nhớ đo trên quy mô lớn

Gmail đã đo lường JavaScript của họ để sử dụng API Performance.memory nhằm thu thập thông tin bộ nhớ khoảng 30 phút một lần. Vì nhiều người dùng Gmail rời khỏi ứng dụng trong nhiều ngày cùng một lúc, nên nhóm có thể theo dõi mức tăng trưởng bộ nhớ theo thời gian cũng như số liệu thống kê tổng thể về mức sử dụng bộ nhớ. Trong vòng vài ngày sau khi thiết lập để Gmail thu thập thông tin bộ nhớ từ một nhóm người dùng lấy mẫu ngẫu nhiên, họ đã có đủ dữ liệu để hiểu rõ mức độ phổ biến của các vấn đề về bộ nhớ ở những người dùng thông thường. Họ thiết lập một đường cơ sở và sử dụng luồng dữ liệu đến để theo dõi tiến trình hướng tới mục tiêu giảm mức sử dụng bộ nhớ. Cuối cùng, dữ liệu này cũng sẽ được dùng để phát hiện lỗi hồi quy bộ nhớ.

Ngoài mục đích theo dõi, các phép đo thực địa còn cung cấp thông tin chi tiết sâu sắc về mối tương quan giữa mức sử dụng bộ nhớ và hiệu suất của ứng dụng. Trái với quan điểm phổ biến rằng "bộ nhớ nhiều hơn dẫn đến hiệu suất tốt hơn", nhóm Gmail nhận thấy rằng mức sử dụng bộ nhớ càng lớn thì thời gian trễ càng dài đối với các tác vụ phổ biến trong Gmail. Nhờ tiết lộ này, họ trở nên có động lực kiểm soát mức sử dụng bộ nhớ hơn bao giờ hết.

Bộ nhớ đo trên quy mô lớn

Xác định vấn đề về bộ nhớ thông qua tiến trình Công cụ cho nhà phát triển

Bước đầu tiên khi giải quyết mọi vấn đề về hiệu suất là chứng minh rằng vấn đề đó tồn tại, tạo một thử nghiệm có thể lặp lại và tiến hành đo lường cơ sở cho vấn đề. Nếu không có chương trình có thể mô phỏng, bạn không thể đo lường vấn đề một cách đáng tin cậy. Nếu không có phương pháp đo lường cơ sở, bạn sẽ không biết được mình đã cải thiện được hiệu suất đến mức nào.

Bảng điều khiển Dòng thời gian cho công cụ cho nhà phát triển là một ứng viên lý tưởng để chứng minh rằng vấn đề tồn tại. Công cụ này cung cấp thông tin tổng quan đầy đủ về nơi người dùng sử dụng thời gian khi tải và tương tác với ứng dụng web hoặc trang của bạn. Tất cả các sự kiện, từ tải tài nguyên cho đến phân tích cú pháp JavaScript, tính toán kiểu, tạm dừng thu gom rác và vẽ lại được lên biểu đồ trên dòng thời gian. Nhằm mục đích điều tra các vấn đề về bộ nhớ, bảng điều khiển Dòng thời gian cũng có chế độ Bộ nhớ giúp theo dõi tổng bộ nhớ được phân bổ, số nút DOM, số đối tượng cửa sổ và số lượng trình nghe sự kiện được phân bổ.

Chứng minh một vấn đề tồn tại

Bắt đầu bằng cách xác định một chuỗi hành động mà bạn nghi ngờ là bị rò rỉ bộ nhớ. Bắt đầu ghi lại tiến trình và thực hiện trình tự hành động. Dùng nút thùng rác ở dưới cùng để buộc thu gom toàn bộ rác. Nếu sau một vài lần lặp lại, bạn thấy biểu đồ có hình răng cưa, tức là bạn đang phân bổ nhiều đối tượng đã tồn tại trong thời gian ngắn. Nhưng nếu chuỗi hành động dự kiến sẽ không dẫn đến bất kỳ bộ nhớ được giữ nào, và số nút DOM không giảm trở lại đường cơ sở nơi bạn bắt đầu, thì bạn có lý do chính đáng để nghi ngờ có sự cố rò rỉ.

Biểu đồ hình răng cưa

Sau khi xác nhận rằng vấn đề tồn tại, bạn có thể yêu cầu trợ giúp để xác định nguồn gây ra sự cố từ Trình phân tích vùng nhớ khối xếp cho nhà phát triển.

Phát hiện sự cố rò rỉ bộ nhớ bằng Trình phân tích vùng nhớ khối xếp cho công cụ cho nhà phát triển

Bảng điều khiển Trình phân tích tài nguyên cung cấp cả trình phân tích CPU và trình phân tích vùng nhớ khối xếp. Tính năng lập hồ sơ vùng nhớ khối xếp hoạt động bằng cách chụp nhanh biểu đồ đối tượng. Trước khi chụp ảnh nhanh, cả thế hệ trẻ và thế hệ cũ đều được thu gom rác. Nói cách khác, bạn sẽ chỉ thấy các giá trị đã hoạt động khi ảnh chụp nhanh được chụp.

Có quá nhiều chức năng trong trình phân tích vùng nhớ khối xếp trong bài viết này nhưng bạn có thể tìm thấy tài liệu chi tiết trên trang web dành cho nhà phát triển Chrome. Ở đây, chúng ta sẽ tập trung vào trình phân tích Phân bổ vùng nhớ khối xếp.

Sử dụng Trình phân tích bộ nhớ khối xếp

Trình phân tích vùng nhớ khối xếp kết hợp thông tin tổng quan nhanh chi tiết của Trình phân tích vùng nhớ khối xếp với việc cập nhật và theo dõi dần dần bảng điều khiển Dòng thời gian. Mở bảng điều khiển Hồ sơ, bắt đầu hồ sơ Record Heap Allocations (Ghi lại quá trình phân bổ bộ nhớ), thực hiện một chuỗi các hành động, sau đó dừng quá trình ghi để phân tích. Trình phân tích tài nguyên phân bổ sẽ chụp nhanh vùng nhớ khối xếp theo định kỳ trong suốt quá trình ghi (cứ 50 mili giây một lần!) và một ảnh chụp nhanh cuối cùng khi kết thúc quá trình ghi.

Trình phân tích vùng nhớ khối xếp

Các thanh ở trên cùng cho biết thời điểm tìm thấy đối tượng mới trong vùng nhớ khối xếp. Chiều cao của mỗi thanh tương ứng với kích thước của các đối tượng được phân bổ gần đây và màu của các thanh cho biết liệu các đối tượng đó có còn hoạt động trong ảnh chụp nhanh của vùng nhớ khối xếp cuối cùng hay không: các thanh màu xanh dương biểu thị các đối tượng vẫn hoạt động ở cuối tiến trình, các thanh màu xám biểu thị các đối tượng đã được phân bổ trong tiến trình, nhưng đã được thu gom rác.

Trong ví dụ trên, một hành động được thực hiện 10 lần. Chương trình mẫu sẽ lưu 5 đối tượng vào bộ nhớ đệm, do đó, dự kiến sẽ có 5 thanh màu xanh dương cuối cùng. Nhưng thanh màu xanh lam ngoài cùng bên trái cho thấy có thể có vấn đề. Sau đó, bạn có thể sử dụng các thanh trượt trong tiến trình ở trên để phóng to ảnh chụp nhanh cụ thể và xem các đối tượng được phân bổ gần đây tại thời điểm đó. Khi nhấp vào một đối tượng cụ thể trong vùng nhớ khối xếp, cây giữ lại của đối tượng đó sẽ xuất hiện ở phần dưới cùng của ảnh chụp nhanh của vùng nhớ khối xếp. Việc kiểm tra đường dẫn giữ lại đến đối tượng sẽ cung cấp cho bạn đủ thông tin để hiểu tại sao đối tượng không được thu thập, đồng thời bạn có thể thực hiện các thay đổi cần thiết về mã để xoá tham chiếu không cần thiết.

Giải quyết khủng hoảng bộ nhớ của Gmail

Bằng cách sử dụng các công cụ và kỹ thuật được thảo luận ở trên, nhóm Gmail có thể xác định một số loại lỗi: bộ nhớ đệm không bị ràng buộc, các mảng lệnh gọi lại tăng dần vô hạn đang chờ sự kiện xảy ra mà không bao giờ thực sự xảy ra và trình nghe sự kiện vô tình giữ lại các mục tiêu của mình. Bằng cách khắc phục các vấn đề này, mức sử dụng bộ nhớ tổng thể của Gmail đã giảm đáng kể. Người dùng trong 99% phần trăm đã sử dụng ít bộ nhớ hơn 80% so với trước đây và mức sử dụng bộ nhớ của người dùng trung bình giảm gần 50%.

Mức sử dụng bộ nhớ Gmail

Vì Gmail dùng ít bộ nhớ hơn, nên thời gian chờ tạm dừng GC đã giảm, nhờ đó nâng cao trải nghiệm tổng thể của người dùng.

Ngoài ra, xin lưu ý rằng nhờ nhóm Gmail thu thập số liệu thống kê về mức sử dụng bộ nhớ, họ đã phát hiện được các lỗi hồi quy thu thập rác trong Chrome. Cụ thể, hai lỗi phân mảnh đã được phát hiện khi dữ liệu bộ nhớ của Gmail bắt đầu cho thấy sự gia tăng đáng kể trong khoảng cách lớn hơn giữa tổng bộ nhớ được phân bổ và bộ nhớ trực tiếp.

Kêu gọi hành động

Hãy tự hỏi những điều sau:

  1. Ứng dụng của tôi đang sử dụng bao nhiêu bộ nhớ? Có thể bạn đang sử dụng quá nhiều bộ nhớ trái ngược với niềm tin phổ biến của người dùng, điều này có thể gây ảnh hưởng xấu đến hiệu suất tổng thể của ứng dụng. Rất khó để biết chính xác số liệu chính xác là gì, nhưng hãy nhớ xác minh rằng bất kỳ việc lưu thêm nào vào bộ nhớ đệm mà trang của bạn đang sử dụng đều có tác động có thể đo lường được về hiệu suất.
  2. Trang của tôi có bị rò rỉ không? Nếu trang của bạn bị rò rỉ bộ nhớ, thì điều này không chỉ ảnh hưởng đến hiệu suất của trang mà còn ảnh hưởng đến các thẻ khác. Hãy sử dụng công cụ theo dõi vật thể để giúp thu hẹp mọi nơi rò rỉ dữ liệu.
  3. Google thu hồi trang của tôi với tần suất như thế nào? Bạn có thể xem mọi tình trạng tạm dừng của GC bằng cách sử dụng bảng Dòng thời gian trong Công cụ cho nhà phát triển Chrome. Nếu trang của bạn thường xuyên bị thu thập dữ liệu, có khả năng bạn đang phân bổ quá thường xuyên, rời bỏ bộ nhớ dành cho thế hệ trẻ của bạn.

Kết luận

Chúng tôi bắt đầu trong một cuộc khủng hoảng. Trình bày các kiến thức cơ bản chính về việc quản lý bộ nhớ trong JavaScript và V8 nói riêng. Bạn đã tìm hiểu cách sử dụng các công cụ này, bao gồm cả tính năng theo dõi đối tượng mới có trong các bản dựng mới nhất của Chrome. Với kiến thức này, nhóm Gmail đã giải quyết được vấn đề về mức sử dụng bộ nhớ của họ và cải thiện hiệu suất. Bạn có thể làm tương tự với các ứng dụng web!