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 đo lường được, bạn sẽ không thể cải thiện được.

Lord Kelvin

Để làm cho 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 hiệu suất, nhưng điều 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ái nhìn toàn diện, bạn phải nắm bắt sắc thái trong các hoạt động trên 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à những 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 mỗi 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ụ about:tracing của Chrome cung cấp cho bạn thông tin về tất cả các hoạt động của Chrome trong một khoảng thời gian với mức độ chi tiết cao đến mức mà ban đầu bạn có thể cảm thấy quá tải. Nhiều chức năng trong Chrome được thiết lập để theo dõi ngay từ đầu, do đó bạn vẫn có thể sử dụng about:tracing để theo dõi hiệu suất 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ề đo lường thủ công JS)

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

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

Thông qua công cụ theo dõi, bạn có thể bắt đầu ghi hình, chạy trò chơi trong vài giây rồi xem dữ liệu theo dõi. Sau đây là ví dụ về hình thức 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á khó hiểu. Hãy cùng nói về cách đọc dữ liệu này.

Mỗi hàng đại diện cho một quy trình được lập hồ sơ, trục trái-phải biểu thị thời gian và mỗi hộp được tô 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 thành phần thú vị nhất khi lập hồ sơ trò chơi là CrGpuMain, cho biết chức năng của Đơn vị xử lý đồ hoạ (GPU) và CrRendererMain. Mỗi dấu vết chứa các dòng CrRendererMain cho từng thẻ đang mở trong khoảng thời gian theo dõi (bao gồm cả 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 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ó một cách chỉn chu để chọn ra ứ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 thủ công mã của mình bằng 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 đúng CrRendererMain sẽ dễ dàng hơn. Tuy nhiên, có thể vẫn có các hàng CrRendererMain cho các quy trình không phải trò chơi của bạn.

Tìm khung hình của bạn

Sau khi bạn xác định được hàng chính xác trong công cụ theo dõi cho trò chơi của mình, 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 lại theo 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 cứ sau 16 mili giây nếu trò chơi của bạn đang chạy ở tần số 60Hz.

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

Sau khi xác định được nhịp 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 được văn bản trong các hộp chức năng.

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

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

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

Thêm thẻ theo dõi

Rất may là có một cách thân thiện để thêm công cụ đo lường thủ công vào mã của bạn nhằm 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");

Đoạn 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 dòng chữ "cập nhật" và "kết xuất" cho biết thời gian đã trôi qua giữa lệnh gọi bắt đầu và lệnh gọi 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 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 lập hồ sơ 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 được điều gì khiến trò chơi của bạn bị chậm, bạn cần xem cách cân bằng công việc giữa hai tài nguyên này.

Trước tiên, hãy tìm dòng trên thành phần hiển thị theo dõi có tên 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 ở trạng thái rảnh trong hầu hết mỗi khung hình 16 mili giây.

Khung hiển thị theo dõi thực sự hữu ích khi bạn có một trò chơi đang chạy chậm và không biết chắc mình đang dùng hết tài nguyên nào. Việc xem xét mối liên hệ giữa các đường GPU và CPU là yếu tố then chốt để gỡ lỗi. Hãy lấy ví dụ tương tự như trước, nhưng thêm một chút thao tá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 như sau:

Dấu vết GPU và CPU

Dấu vết này cho chúng tôi biết điều gì? Chúng ta có thể thấy rằng khung hình được chụp trong hình tăng từ khoảng 2270 mili giây đến 2320 mili giây, có nghĩa là mỗi khung hình mất khoảng 50 mili giây (tốc độ khung hình là 20Hz). Bạn có thể thấy các mảnh của hộp 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 hình hoàn toàn bị chi phối bởi chính bản cập nhật.

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á đoạn mã này, bạn nên tìm những thao tác có thể thực hiện trong mã của chương trình đổ bóng rồi di chuyển chúng sang GPU để tận dụng tài nguyên một cách hiệu quả nhất.

Còn trường hợp mã đổ 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ác 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 đỏ 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 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 có tốc độ chậm

Một lần nữa, hãy lưu ý đến thời lượng 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 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 truy cập vào mã trò chơi và bắt đầu cố gắng 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 tốt vì logic trong vòng lặp trò chơi không phải là thứ đang tiêu tốn thời gian. Trên thực tế, dấu vết này chỉ ra rằng việc tăng cường hoạt động của CPU vào mỗi khung hình về cơ bản sẽ là "miễn phí" trong đó CPU luôn ở trạng thái rảnh, nên việc CPU xử lý nhiều công việc hơn sẽ không ảnh hưởng đến thời lượng 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 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ề trò chơi được xây dựng bằng công nghệ web mở là bạn có thể biết được điều gì đang diễn ra trên những sản phẩm mình yêu thích. Nếu bạn muốn thử nghiệm các công cụ tạo hồ sơ, 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à lập hồ sơ bằng about:tracing. Đây là một dấu vết mẫu được lấy từ trò chơi Skid Racer tuyệt vời của WebGL.

Theo dõi một trò chơi 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, nghĩa là tốc độ khung hình vào khoảng 50 khung hình/giây. 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 các ví dụ thực tế về trò chơi WebGL sẽ như thế nào, hãy thử chơi với một số tiêu đề 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 của mình chạy ở tốc độ 60Hz, thì đối với mỗi khung hình, tất cả các hoạt động của bạn phải phù hợp với 16 mili giây 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à bạn có thể chuyển đổi công việc giữa chúng để tối đa hoá hiệu suất. Chế độ xem about:tracing của Chrome là một công cụ vô cùng hữu ích giúp bạn hiểu rõ hơn về hoạt động thực sự của mã và giúp bạn tối đa hoá thời gian phát triển 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, phiên bản giai đoạn đầu 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. Đó 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 các trò chơi trên Chrome: