Lập hồ sơ trò chơi WebGL của bạn với cờ about:theo dõi

Lilli Thompson
Lilli Thompson

Nếu không thể đo lường thì bạn không thể cải thiện.

Lord Kelvin

Để trò chơi HTML5 chạy nhanh hơn, trước tiên, bạn phải xác định chính xác nút thắt cổ chai về hiệu suất, nhưng việc này có thể khó khăn. Việc đánh giá dữ liệu khung hình/giây (FPS) là bước khởi đầu, nhưng để có được thông tin toàn diện, bạn phải nắm bắt được các sắc thái trong các hoạt động của Chrome.

Công cụ about:tracing cung cấp thông tin chi tiết giúp bạn tránh các giải pháp vội vàng nhằm cải thiện hiệu suất, nhưng về cơ bản là những phỏng đoán có thiện chí. Bạn sẽ tiết kiệm được nhiều thời gian và công sức, hiểu rõ hơn về những gì Chrome đang làm với từng khung hình và sử dụng thông tin này để tối ưu hoá trò chơi của mình.

Xin chào about:tracing!

Công cụ about:tracing của Chrome cung cấp cho bạn thông tin về tất cả hoạt động của Chrome trong một khoảng thời gian với mức độ chi tiết đến mức ban đầu bạn có thể thấy choáng ngợp. Nhiều hàm trong Chrome được đo lường để theo dõi ngay từ đầu, vì vậy, bạn vẫn có thể sử dụng about:tracing để theo dõi hiệu suất mà không cần đo lường thủ công. (Xem phần sau về cách đo lường JS theo cách thủ công)

Để xem chế độ xem theo dõi, bạn chỉ cần nhập "about:tracing" vào hộp thông tin tổng hợp (thanh địa chỉ) của Chrome.

Thanh địa chỉ của Chrome
Nhập "about:tracing" vào hộp tìm kiếm của Chrome

Trong công cụ theo dõi, bạn có thể bắt đầu ghi, chạy trò chơi trong vài giây rồi xem dữ liệu theo dõi. Dưới đây là ví dụ về dữ liệu có thể xuất hiện:

Kết quả theo dõi đơn giản
Kết quả theo dõi đơn giản

Vâng, đúng là khó hiểu. Hãy cùng tìm hiểu cách đọc tệp này.

Mỗi hàng đại diện cho một quy trình đang được phân tích, trục trái-phải cho biết thời gian và mỗi hộp màu là một lệnh gọi hàm được đo lường. Có các hàng cho một số loại tài nguyên khác nhau. Những tệp thú vị nhất để phân tích tài nguyên trò chơi là CrGpuMain (cho biết hoạt động của Bộ xử lý đồ hoạ (GPU)) và CrRendererMain. Mỗi dấu vết chứa các dòng CrRendererMain cho mỗi thẻ đang mở trong khoảng thời gian theo dõi (bao gồm cả chính thẻ about:tracing).

Khi đọc dữ liệu theo dõi, nhiệm vụ đầu tiên của bạn là xác định hàng CrRendererMain nào tương ứng với trò chơi của bạn.

Kết quả theo dõi đơn giản được làm nổi bật
Kết quả theo dõi đơn giản được làm nổi bật

Trong ví dụ này, hai ứng cử viên là: 2216 và 6516. Rất tiếc, hiện không có cách nào hoàn hảo để chọn ứng dụng của bạn, ngoại trừ việc tìm dòng đang thực hiện nhiều lần cập nhật định kỳ (hoặc nếu bạn đã đo lường mã theo cách thủ công bằng các điểm theo dõi, hãy tìm dòng chứa dữ liệu theo dõi). Trong ví dụ này, có vẻ như 6516 đang chạy một vòng lặp chính theo tần suất cập nhật. Nếu bạn đóng tất cả các thẻ khác trước khi bắt đầu theo dõi, bạn sẽ dễ dàng tìm thấy CrRendererMain chính xác hơn. Tuy nhiên, vẫn có thể có các hàng CrRendererMain cho các quy trình khác ngoài trò chơi của bạn.

Tìm khung hình

Sau khi bạn đã tìm thấy hàng chính xác trong công cụ theo dõi cho trò chơi, bước tiếp theo là tìm vòng lặp chính. Vòng lặp chính trông giống như một mẫu lặp lại trong dữ liệu theo dõi. Bạn có thể di chuyển trong dữ liệu theo dõi bằng cách sử dụng các phím W, A, S, D: A và D để di chuyển sang trái hoặc phải (trước và sau trong thời gian) và W và S để phóng to và thu nhỏ dữ liệu. Bạn sẽ mong đợi vòng lặp chính của mình là một mẫu lặp lại sau mỗi 16 mili giây nếu trò chơi của bạn đang chạy ở tốc độ 60Hz.

Có vẻ như có 3 khung thực thi
Trông giống như ba khung thực thi

Sau khi xác định được nhịp tim của trò chơi, bạn có thể tìm hiểu chính xác mã của mình đang làm gì ở mỗi khung hình. Sử dụng các phím W, A, S, D để phóng to cho đến khi bạn có thể đọc văn bản trong các hộp hàm.

Tìm hiểu sâu về khung thực thi
Tìm hiểu chuyên sâu về khung thực thi

Tập hợp các hộp này cho thấy một loạt lệnh gọi hàm, trong đó mỗi lệnh gọi được biểu thị bằng một hộp có màu. Mỗi hàm được gọi bởi hộp phía trên hàm đó, vì vậy, trong trường hợp này, bạn có thể thấy MessageLoop::RunTask gọi RenderWidget::OnSwapBuffersComplete, sau đó gọi RenderWidget::DoDeferredUpdate, v.v. Khi đọc dữ liệu này, bạn có thể xem toàn bộ thông tin về lệnh gọi nào gọi lệnh gọi nào và thời gian thực thi mỗi lệnh gọi.

Nhưng đây là phần hơi khó khăn. Thông tin do about:tracing hiển thị là các lệnh gọi hàm thô từ mã nguồn Chrome. Bạn có thể đoán được chức năng của từng hàm qua tên hàm, nhưng thông tin này không thực sự thân thiện với người dùng. Việc xem luồng tổng thể của khung hình sẽ rất hữu ích, nhưng bạn cần một thứ dễ đọc hơn để thực sự tìm hiểu xem điều gì đang diễn ra.

Thêm thẻ theo dõi

May mắn thay, có một cách thân thiện để thêm tính năng đo lường thủ công vào mã của bạn để tạo dữ liệu theo dõi: console.timeconsole.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

Mã trên tạo các hộp mới trong tên chế độ xem theo dõi với các thẻ được chỉ định, vì vậy, nếu chạy lại ứng dụng, bạn sẽ thấy các hộp "cập nhật" và "hiển thị" cho biết thời gian đã trôi qua giữa các lệnh gọi bắt đầu và kết thúc cho mỗi thẻ.

Thẻ được thêm theo cách thủ công
Thẻ được thêm theo cách thủ công

Bằng cách này, bạn có thể tạo dữ liệu theo dõi mà con người có thể đọc được để theo dõi các điểm nóng trong mã.

GPU hay CPU?

Với đồ hoạ tăng tốc phần cứng, một trong những câu hỏi quan trọng nhất mà bạn có thể đặt ra trong quá trình phân tích tài nguyên là: Mã này có bị ràng buộc bởi GPU hay CPU? Với mỗi khung hình, bạn sẽ thực hiện một số công việc kết xuất trên GPU và một số logic trên CPU; để hiểu điều gì khiến trò chơi của bạn bị chậm, bạn cần xem cách công việc được cân bằng giữa hai tài nguyên này.

Trước tiên, hãy tìm dòng trên chế độ xem theo dõi có tên là CrGPUMain. Dòng này cho biết GPU có bận hay không tại một thời điểm cụ thể.

Dấu vết GPU và CPU

Bạn có thể thấy rằng mọi khung hình của trò chơi đều khiến CPU hoạt động trong CrRendererMain cũng như trên GPU. Dấu vết trên cho thấy một trường hợp sử dụng rất đơn giản, trong đó cả CPU và GPU đều ở trạng thái rảnh trong hầu hết mỗi khung 16 mili giây.

Chế độ xem theo dõi thực sự hữu ích khi bạn có một trò chơi chạy chậm và bạn không chắc mình đang sử dụng hết tài nguyên nào. Việc xem xét mối quan hệ giữa các dòng GPU và CPU là chìa khoá để gỡ lỗi. Lấy ví dụ tương tự như trước, nhưng thêm một chút công việc trong vòng lặp cập nhật.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Bây giờ, bạn sẽ thấy một dấu vết có dạng như sau:

Dấu vết GPU và CPU

Dấu vết này cho chúng ta biết điều gì? Chúng ta có thể thấy rằng khung hình được chụp từ khoảng 2270 mili giây đến 2320 mili giây, nghĩa là mỗi khung hình mất khoảng 50 mili giây (tốc độ khung hình là 20 Hz). Bạn có thể thấy các ô màu đại diện cho hàm kết xuất bên cạnh hộp cập nhật, nhưng khung này hoàn toàn do chính bản cập nhật chiếm ưu thế.

Ngược lại với những gì đang diễn ra trên CPU, bạn có thể thấy GPU vẫn ở trạng thái rảnh trong hầu hết các khung hình. Để tối ưu hoá mã này, bạn có thể tìm các thao tác có thể thực hiện trong mã chương trình đổ bóng và chuyển các thao tác đó sang GPU để khai thác tối đa tài nguyên.

Còn khi mã chương trình đổ bóng bị chậm và GPU bị quá tải thì sao? Điều gì sẽ xảy ra nếu chúng ta xoá công việc không cần thiết khỏi CPU và thay vào đó thêm một số công việc vào mã chương trình đổ bóng mảnh. Dưới đây là một chương trình đổ bóng mảnh tốn kém không cần thiết:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

Dấu vết của mã sử dụng chương trình đổ bóng đó sẽ trông như thế nào?

Dấu vết GPU và CPU khi sử dụng mã GPU chậm
Dấu vết GPU và CPU khi sử dụng mã GPU chậm

Xin nhắc lại, hãy lưu ý thời lượng của một khung hình. Ở đây, mẫu lặp lại từ khoảng 2750 mili giây đến 2950 mili giây, thời lượng là 200 mili giây (tốc độ khung hình khoảng 5 Hz). Dòng CrRendererMain gần như trống hoàn toàn, nghĩa là CPU thường ở trạng thái rảnh, trong khi GPU bị quá tải. Đây là dấu hiệu chắc chắn cho thấy chương trình đổ bóng của bạn quá nặng.

Nếu không biết chính xác nguyên nhân gây ra tốc độ khung hình thấp, bạn có thể quan sát bản cập nhật 5 Hz và muốn tìm hiểu mã trò chơi để bắt đầu tối ưu hoá hoặc xoá logic trò chơi. Trong trường hợp này, việc đó sẽ hoàn toàn không có tác dụng vì logic trong vòng lặp trò chơi không phải là yếu tố làm tiêu tốn thời gian. Trên thực tế, dấu vết này cho thấy việc thực hiện nhiều công việc hơn cho CPU trên mỗi khung hình về cơ bản sẽ "miễn phí" vì CPU đang ở trạng thái rảnh, vì vậy việc tăng công việc cho CPU sẽ không ảnh hưởng đến thời gian của khung hình.

Ví dụ thực tế

Bây giờ, hãy xem dữ liệu theo dõi từ một trò chơi thực tế trông như thế nào. Một trong những điều thú vị về các trò chơi được xây dựng bằng công nghệ web mở là bạn có thể xem những gì đang diễn ra trong các sản phẩm mà bạn yêu thích. Nếu muốn thử nghiệm các công cụ phân tích tài nguyên, bạn có thể chọn một trò chơi WebGL yêu thích trên Cửa hàng Chrome trực tuyến và phân tích tài nguyên trò chơi đó bằng about:tracing. Đây là một dấu vết mẫu được lấy từ trò chơi WebGL tuyệt vời Skid Racer.

Theo dõi một trò chơi thực tế
Theo dõi một trò chơi thực tế

Có vẻ như mỗi khung hình mất khoảng 20 mili giây, tức là tốc độ khung hình khoảng 50 khung hình/giây. Bạn có thể thấy công việc được cân bằng giữa CPU và GPU, nhưng GPU là tài nguyên được yêu cầu nhiều nhất. Nếu bạn muốn xem ví dụ thực tế về việc lập hồ sơ cho các trò chơi WebGL, hãy thử chơi một số trò chơi trên Cửa hàng Chrome trực tuyến được tạo bằng WebGL, bao gồm:

Kết luận

Nếu bạn muốn trò chơi chạy ở tốc độ 60Hz, thì đối với mỗi khung hình, tất cả các thao tác của bạn phải vừa với 16 mili giây của CPU và 16 mili giây của GPU. Bạn có thể sử dụng song song hai tài nguyên và có thể chuyển công việc giữa hai tài nguyên đó để tối đa hoá hiệu suất. Chế độ xem about:tracing của Chrome là một công cụ vô giá để biết thông tin chi tiết về những gì mã của bạn đang thực sự làm và sẽ giúp bạn tối đa hoá thời gian phát triển bằng cách giải quyết đúng vấn đề.

Tiếp theo là gì?

Ngoài GPU, bạn cũng có thể theo dõi các phần khác của môi trường thời gian chạy Chrome. Chrome Canary, phiên bản Chrome ở giai đoạn đầu, được đo lường để theo dõi IO, IndexedDB và một số hoạt động khác. Bạn nên đọc bài viết này về Chromium để hiểu rõ hơn về trạng thái hiện tại của các sự kiện theo dõi.

Nếu bạn là nhà phát triển trò chơi trên web, hãy nhớ xem video bên dưới. Đây là bản trình bày của nhóm Cố vấn nhà phát triển trò chơi của Google tại GDC 2012 về việc tối ưu hoá hiệu suất cho các trò chơi trên Chrome: