Làm thế nào để hưởng lợi từ bộ đệm cpu trong một công cụ trò chơi hệ thống thành phần thực thể?


15

Tôi thường đọc trong các tài liệu công cụ trò chơi ECS là một kiến ​​trúc tốt để sử dụng bộ đệm cpu một cách khôn ngoan.

Nhưng tôi không thể hiểu làm thế nào chúng ta có thể hưởng lợi từ bộ đệm cpu.

Nếu các thành phần được lưu trong một mảng (hoặc một nhóm), trong bộ nhớ liền kề, đó là cách tốt để sử dụng bộ đệm cpu NHƯNG chỉ khi chúng ta đọc các thành phần một cách tuần tự.

Khi chúng ta sử dụng các hệ thống, chúng cần danh sách các thực thể là danh sách các thực thể có các thành phần với các loại cụ thể.

Nhưng các danh sách này cung cấp cho các thành phần một cách ngẫu nhiên, không tuần tự.

Vậy làm thế nào để thiết kế một ECS để tối đa hóa bộ nhớ cache?

BIÊN TẬP :

Ví dụ, một hệ thống Physic cần một danh sách thực thể cho thực thể có các thành phần RigidBody và Transform (Có một nhóm cho RigidBody và một nhóm cho các thành phần Transform).

Vì vậy, vòng lặp của nó để cập nhật các thực thể sẽ như thế này:

for (Entity eid in entitiesList) {
    // Get rigid body component
    RigidBody *rigidBody = entityManager.getComponentFromEntity<RigidBody>(eid);

    // Get transform component
    Transform *transform = entityManager.getComponentFromEntity<Transform>(eid);

    // Do something with rigid body and transform component
}

Vấn đề là thành phần RigidBody của thực thể1 có thể nằm ở chỉ số 2 của nhóm và thành phần Tranform của thực thể 1 tại chỉ số 0 của nhóm (vì một số thực thể có thể có một số thành phần chứ không phải là thành phần khác và vì thêm / xóa thực thể / thành phần ngẫu nhiên).

Vì vậy, ngay cả khi các thành phần tiếp giáp trong bộ nhớ, chúng được đọc ngẫu nhiên và do đó nó sẽ bị mất bộ nhớ cache nhiều hơn, phải không?

Trừ khi có một cách để tìm nạp trước các thành phần tiếp theo trong vòng lặp?


bạn có thể chỉ cho chúng tôi cách bạn phân bổ từng thành phần không?
Concept3d

Với bộ cấp phát nhóm đơn giản và trình quản lý Xử lý để có tham chiếu thành phần để quản lý việc di chuyển các thành phần trong nhóm (để giữ các thành phần tiếp giáp trong bộ nhớ).
Johnmph

Vòng lặp ví dụ của bạn cho rằng các cập nhật thành phần được xen kẽ cho mỗi thực thể. Trong nhiều trường hợp, có thể cập nhật hàng loạt các thành phần theo loại thành phần (ví dụ: cập nhật tất cả các thành phần cứng nhắc trước, sau đó cập nhật tất cả các biến đổi với dữ liệu cứng nhắc đã hoàn thành, sau đó cập nhật tất cả dữ liệu kết xuất với các biến đổi mới ...) - điều này có thể cải thiện bộ đệm sử dụng cho từng cập nhật thành phần. Tôi nghĩ loại cấu trúc này là những gì Nick Wiggill đang đề xuất bên dưới.
DMGregory

Trên thực tế, đây là ví dụ tồi tệ của tôi, đó là hệ thống "cập nhật tất cả các biến đổi với hệ thống dữ liệu cơ thể cứng đã hoàn thành" so với hệ thống Physic. Nhưng vấn đề vẫn giữ nguyên, trong các hệ thống này (cập nhật biến đổi với thân cứng, cập nhật kết xuất với biến đổi, ...), chúng ta sẽ cần phải có nhiều hơn một loại thành phần cùng một lúc.
Johnmph

Không chắc chắn nếu điều này có thể có liên quan quá? gamasutra.com/view/feature/6345/
DMGregory

Câu trả lời:


13

Bài báo của Mick West giải thích quá trình tuyến tính hóa dữ liệu thành phần thực thể. Nó đã làm việc cho loạt Tony Hawk, nhiều năm trước, trên phần cứng ít ấn tượng hơn chúng ta ngày nay, để cải thiện hiệu suất rất nhiều. Về cơ bản, ông đã sử dụng các mảng toàn cầu, được phân bổ trước cho từng loại dữ liệu thực thể riêng biệt (vị trí, điểm số và không có gì) và tham chiếu từng mảng trong một giai đoạn riêng biệt của update()chức năng toàn hệ thống . Bạn có thể giả sử rằng dữ liệu cho mỗi thực thể sẽ có cùng một chỉ mục mảng trong mỗi mảng toàn cầu này, vì vậy, ví dụ, nếu trình phát được tạo trước, nó có thể có dữ liệu của nó [0]trong mỗi mảng.

Thậm chí cụ thể hơn để tối ưu hóa bộ đệm, các slide của Christer Ericsson cho C và C ++.

Để cung cấp thêm một chút chi tiết, bạn nên cố gắng sử dụng các khối bộ nhớ liền kề (được phân bổ dễ dàng nhất dưới dạng mảng) cho mỗi loại dữ liệu (ví dụ: vị trí, xy và z), để đảm bảo địa phương tham chiếu tốt, sử dụng riêng từng khối dữ liệu đó update()các giai đoạn vì lợi ích của địa phương tạm thời, tức là để đảm bảo bộ đệm không bị xóa thông qua thuật toán LRU của phần cứng trước khi bạn sử dụng lại bất kỳ dữ liệu nào bạn định sử dụng lại, trong một update()cuộc gọi nhất định . Như bạn đã ngụ ý, điều bạn không muốn làm là phân bổ các thực thể và thành phần của mình dưới dạng các đối tượng riêng biệt thông qua new, vì dữ liệu của các loại khác nhau trên mỗi thực thể sau đó sẽ được xen kẽ, làm giảm tính tham chiếu.

Nếu bạn có sự phụ thuộc lẫn nhau giữa các thành phần (dữ liệu) sao cho bạn hoàn toàn không có khả năng tách một số dữ liệu khỏi dữ liệu được liên kết (ví dụ: Transform + Vật lý, Biến đổi + Trình kết xuất) thì bạn có thể chọn sao chép dữ liệu Chuyển đổi trong cả mảng Vật lý và Trình kết xuất , đảm bảo rằng tất cả dữ liệu thích hợp phù hợp với độ rộng dòng bộ đệm cho từng hoạt động quan trọng về hiệu năng.

Cũng cần nhớ rằng bộ đệm L2 và L3 (nếu bạn có thể giả sử những bộ đệm này cho nền tảng đích của mình) sẽ làm rất nhiều để giảm bớt các vấn đề mà bộ đệm L1 có thể gặp phải, chẳng hạn như độ rộng dòng hạn chế. Vì vậy, ngay cả khi bỏ lỡ L1, đây là những mạng lưới an toàn thường sẽ ngăn chặn chú thích vào bộ nhớ chính, đó là các lệnh có cường độ chậm hơn so với chú thích đến bất kỳ mức bộ nhớ cache nào.

Lưu ý về việc ghi dữ liệu Viết không gọi ra bộ nhớ chính. Theo mặc định, các hệ thống ngày nay đã bật bộ đệm ghi lại : ghi một giá trị chỉ ghi nó vào bộ đệm (ban đầu), không ghi vào bộ nhớ chính, do đó bạn sẽ không bị tắc nghẽn bởi điều này. Chỉ khi dữ liệu được yêu cầu từ bộ nhớ chính (sẽ không xảy ra trong khi bộ nhớ cache) và cũ, bộ nhớ chính đó sẽ được cập nhật từ bộ đệm.


1
Chỉ là một lưu ý cho bất kỳ ai có thể chưa quen với C ++: std::vectorvề cơ bản là một mảng có thể thay đổi linh hoạt và do đó cũng liền kề (thực tế trong các phiên bản C ++ cũ hơn và de jure trong các phiên bản C ++ mới hơn). Một số triển khai std::dequecũng "đủ liền kề" (mặc dù không phải của Microsoft).
Sean Middleditch

2
@Johnmph Khá đơn giản: Nếu bạn không có địa phương tham chiếu, bạn không có gì cả. Nếu hai phần dữ liệu có liên quan chặt chẽ với nhau (chẳng hạn như thông tin không gian và vật lý), tức là chúng được xử lý cùng nhau, thì bạn có thể phải nén chúng thành một thành phần duy nhất, xen kẽ. Nhưng hãy nhớ rằng bất kỳ logic nào khác (giả sử, AI) tận dụng dữ liệu không gian đó có thể bị ảnh hưởng do dữ liệu không gian không được đưa vào cùng với . Vì vậy, nó phụ thuộc vào những gì đòi hỏi hiệu suất cao nhất (có lẽ là vật lý trong trường hợp của bạn). Điều đó có ý nghĩa?
Kỹ sư

1
@Johnmph vâng, tôi hoàn toàn đồng ý với Nick về cách chúng được lưu trữ trong bộ nhớ, nếu bạn có thực thể với hai con trỏ ở xa trong bộ nhớ mà bạn không có địa phương, chúng phải nằm gọn trong một dòng bộ đệm.
Concept3d

2
@Johnmph: Thật vậy, bài viết của Mick West giả định sự phụ thuộc tối thiểu. Vì vậy: Giảm thiểu phụ thuộc; Sao chép dữ liệu dọc theo các dòng bộ đệm trong đó bạn không thể giảm thiểu các phụ thuộc đó ... ví dụ: bao gồm Transform cùng với cả RigidBody Render; và để phù hợp với các dòng bộ đệm, bạn có thể cần giảm các nguyên tử dữ liệu của mình càng nhiều càng tốt ... điều này có thể đạt được một phần bằng cách đi từ điểm nổi đến điểm cố định (4 byte so với 2 byte) cho mỗi giá trị dấu thập phân. Nhưng bằng cách này hay cách khác, bất kể bạn làm như thế nào, dữ liệu của bạn phải phù hợp với độ rộng dòng bộ đệm như khái niệm 3d đã lưu ý, để có hiệu suất tối đa.
Kỹ sư

2
@Johnmph. Không. Bất cứ khi nào bạn viết Transform data, bạn chỉ cần ghi nó vào cả hai mảng. Đó không phải là những bài viết bạn cần phải lo lắng. Một khi bạn gửi đi một bài viết, nó cũng tốt như vậy. Đó là lần đọc , sau này trong bản cập nhật, khi bạn chạy Vật lý và Trình kết xuất, phải có quyền truy cập vào tất cả dữ liệu thích hợp, ngay lập tức, trong một dòng bộ đệm duy nhất ngay sát CPU và cá nhân. Ngoài ra, nếu bạn thực sự cần tất cả cùng nhau, thì bạn có thể sao chép thêm hoặc đảm bảo vật lý, biến đổi và kết xuất phù hợp với một dòng bộ đệm duy nhất ... 64 byte là phổ biến và thực sự có khá nhiều dữ liệu! ...
Kỹ sư
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.