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

[Tên người]
Llili Thompson

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

Lord Kelvin

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

Công cụ about:theo dõi cung cấp thông tin chi tiết giúp bạn tránh các giải pháp nóng vội nhằm cải thiện hiệu suất, nhưng về cơ bản là phỏng đoán có chủ đích. Bạn sẽ tiết kiệm được rất 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 bạn.

Xin chào về:tracing

Công cụ về:theo dõi của Chrome cung cấp cho bạn một cửa sổ để xem tất cả các hoạt động của Chrome trong một khoảng thời gian với nhiều chi tiết đến mức ban đầu bạn có thể cảm thấy quá 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 hàm về:theo dõi để theo dõi hiệu suất của mình mà không cần thực hiện bất kỳ công cụ đo lường thủ công nào. (Xem phần sau về cách đo lường JS của bạn theo cách thủ công)

Để xem chế độ xem theo dõi, chỉ cần nhập "about:tracing" vào thanh địa chỉ của Chrome (thanh địa chỉ).

Thanh địa chỉ Chrome
Nhập "about:tracing" vào thanh địa chỉ của Chrome

Từ 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ạng của dữ liệu:

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

Vâng, điều đó khó hiểu. Hãy cùng thảo luận về cách đọc biểu mẫu này.

Mỗi hàng đại diện cho một quy trình đang được lập hồ sơ, trục trái, phải biểu thị 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. Hai yếu tố thú vị nhất để lập hồ sơ trò chơi là CrGpuMain, cho thấy những gì Bộ xử lý đồ hoạ (GPU) đang làm và CrRendererMain. Mỗi dấu vết chứa các dòng CrRendererMain cho từng thẻ đang mở trong thời gian theo dõi (bao gồm cả thẻ about:tracing).

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

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 đề xuất là: 2216 và 6516. Rất tiếc, hiện tại không có cách chỉn chu nà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 cập nhật định kỳ (hoặc nếu bạn đã đo lường mã của mình theo cách thủ công với các điểm theo dõi, để tìm dòng chứa dữ liệu theo dõi của bạn). Trong ví dụ này, có vẻ như 6516 đang chạy một vòng lặp chính dựa trên 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, thì việc tìm ra CrRendererMain chính xác sẽ dễ dàng hơn. Tuy nhiên, vẫn có thể có hàng CrRendererMain cho các quy trình khác ngoài trò chơi của bạn.

Đang tìm khung của bạn

Sau khi bạn tìm được hàng chính xác trong công cụ theo dõi dành 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ể điều hướng 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 sang phải (qua thời gian) và W và S để phóng to và thu nhỏ dữ liệu. Vòng lặp chính sẽ 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ần số 60 Hz.

Có vẻ như có 3 khung thực thi
Có vẻ như có 3 khung thực thi

Sau khi đã định vị được nhịp tim của trò chơi, bạn có thể tìm hiểu chính xác hoạt động của mã ở mỗi khung hình. Sử dụng 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.

Đi sâu vào khung thực thi
Đi sâu vào khung thực thi

Tập hợp 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 màu. Mỗi hàm được gọi bởi hộp phía trên, vì vậy trong trường hợp này, bạn có thể thấy MessageLoop::RunTask gọi là RenderWidget::OnSwapBuffersComplete, lần lượt được gọi là RenderWidget::DoDeferredUpdate, v.v. Đọc dữ liệu này, bạn có thể có được cái nhìn đầy đủ về những gì được gọi là gì và mỗi lần thực thi mất bao lâu.

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

Thêm thẻ theo dõi

Rất may là có một cách hay để thêm khả năng đo lường thủ công vào mã của bạn để tạo dữ liệu theo dõi, đó là console.timeconsole.timeEnd.

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

Đoạn mã ở trên sẽ tạo các hộp mới trong tên thành phần hiển thị 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 "update" và "Render" cho biết thời gian đã trôi qua giữa lệnh gọi bắt đầu và kết thúc của 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 phát sóng trong mã của mình.

GPU hay CPU?

Với đồ hoạ được tăng tốc phần cứng, một trong những câu hỏi quan trọng nhất bạn có thể đặt ra trong quá trình phân tích là: Mã này có bị ràng buộc bởi GPU hay CPU không? 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ì đang khiến trò chơi của bạn chậm đi, bạn cần xem cách cân bằng công việc giữa hai tài nguyên.

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

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 không hoạt động trong hầu hết từng khung hình 16 mili giây.

Chế độ xem theo dõi thực sự hữu ích khi một trò chơi của bạn đang chạy chậm và bạn không chắc chắn tài nguyên nào mình đang sử dụng hết. Việc xem xét mối liên hệ giữa các dòng GPU và CPU là yếu tố then chốt để 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");

Lúc này, 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 đi từ khoảng 2270 mili giây đến 2320 mili giây, có nghĩa là mỗi khung hình sẽ mất khoảng 50 mili giây (tốc độ khung hình là 20Hz). Bạn có thể thấy các dải hộp màu biểu thị chức năng kết xuất bên cạnh hộp cập nhật, nhưng khung này lại bị bản cập nhật chi phối hoàn toàn.

Trái ngược 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 mọi 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 rồi di chuyển chúng sang GPU để sử dụng tài nguyên một cách hiệu quả nhất.

Còn trường hợp mã chương trình đổ bóng bị chậm và GPU hoạt động quá mức 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. Sau đây là một chương trình đổ bóng mảnh cực kỳ tốn kém:

#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 đó 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

Một lần nữa, hãy lưu ý thời lượng của khung hình. Ở đây mẫu lặp lại đi từ khoảng 2750ms đến 2950ms, thời lượng 200ms (tốc độ khung hình khoảng 5Hz). Dòng CrRendererMain gần như hoàn toàn trống, có nghĩa là CPU hầu như không hoạt động, trong khi GPU thì 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 thấy quá trình cập nhật 5 Hz và bị lôi cuốn vào đoạn mã trò chơi và bắt đầu tìm cách tối ưu hoá hoặc loại bỏ logic trò chơi. Trong trường hợp này, điều đó hoàn toàn không hiệu quả, vì logic trong vòng lặp trò chơi không phải là thứ tiêu tốn thời gian. Trên thực tế, dấu vết này cho thấy rằng khi CPU làm nhiều việc hơn, mỗi khung hình về cơ bản sẽ là "miễn phí" vì CPU vẫn ở trạng thái rảnh, do đó, việc tăng thêm tác vụ sẽ không ảnh hưởng đến thời gian kết xuất khung hình.

Ví dụ thực tế

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

Theo dõi một trận đấu thực
Theo dõi một trò chơi thực

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

Kết luận

Nếu bạn muốn trò chơi của mình chạy ở tần số 60Hz, thì đối với mỗi khung hình, tất cả các hoạt động của bạn phải vừa với 16 mili giây của CPU và 16 mili giây thời gian GPU. Bạn có hai tài nguyên có thể sử dụng song song và có thể chuyển đổi công việc giữa chúng để tối đa hoá hiệu suất. Chế độ xem về:theo dõi của Chrome là một công cụ vô giá để thu thập 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ăng tối đa thời gian phát triển của mình bằng cách giải quyết các vấn đề phù hợp.

Tiếp theo là gì?

Ngoài GPU, bạn cũng có thể theo dõi các phần khác trong thời gian chạy Chrome. Chrome Canary là phiên bản đầu tiên của Chrome đượ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 trên 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 dưới đây. Đây là bài thuyết trình của nhóm Hỗ trợ 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 trò chơi trên Chrome: