File 1 — Buffer-Model Demo
Đoạn mã minh họa cách sử dụng Buffer-Model trong một kịch bản dữ liệu lồng nhau
(User → Bag → Item). Mặc dù dữ liệu được tổ chức trong một Buffer liên tục,
lập trình viên vẫn có thể thao tác thông qua các thuộc tính và mô hình phân cấp
gần giống với OOP truyền thống.
Mỗi Model chỉ đóng vai trò lớp truy cập dữ liệu (access layer), trong khi dữ liệu
thực tế được lưu trữ bên trong Buffer. Việc gán giá trị cho các trường như
Id, Name, Email hoặc các Model con không tạo thêm object trung gian và không yêu cầu
cấu trúc dữ liệu phân tán trên Heap.
Ví dụ cũng cho thấy dữ liệu có thể được cập nhật nhiều lần với kích thước khác nhau.
Các trường Name hoặc Bag.Name được ghi đè bởi chuỗi dài hơn nhưng mã nguồn phía trên
không thay đổi. Toàn bộ việc quản lý layout dữ liệu được xử lý ở tầng Buffer,
giúp API sử dụng vẫn giữ được sự đơn giản.
Các Model con như BagModel và ItemModel được attach động lên cùng một Buffer.
Nhờ đó toàn bộ cây dữ liệu vẫn dùng chung một vùng nhớ thay vì tạo nhiều object độc lập.
Cách tiếp cận này giúp giảm allocation và cải thiện data locality khi truy cập dữ liệu.
Mục tiêu của Buffer-Model là kết hợp trải nghiệm lập trình quen thuộc của OOP
với cách tổ chức dữ liệu theo hướng Data-Oriented Design (DOD), cho phép dữ liệu
sẵn sàng cho việc xử lý, truyền tải hoặc chia sẻ mà không cần bước chuyển đổi trung gian.
File 2 — Buffer-View Demo
Buffer-View là cơ chế ánh xạ trực tiếp một vùng nhớ liên tục (Buffer) thành một cấu trúc dữ liệu
kiểu struct mà không cần thực hiện quá trình parse, deserialize hay tạo đối tượng mới.
Toàn bộ dữ liệu được thao tác trực tiếp trên vùng nhớ gốc thông qua tham chiếu (ref).
Trong ví dụ trên, phía gửi khởi tạo một đối tượng Monster bằng
InitStruct<Monster>() và ghi dữ liệu trực tiếp vào Buffer.
Các trường lồng nhau như Player, Position và
Stats đều được cập nhật thông qua cùng một vùng nhớ liên tục.
Phía nhận không cần thực hiện bất kỳ thao tác chuyển đổi dữ liệu nào.
Chỉ với lời gọi AsStruct<Monster>(), hệ thống trả về một
tham chiếu đến chính vùng dữ liệu đã được ghi trước đó. Điều này giúp loại bỏ
hoàn toàn chi phí parse và cấp phát bộ nhớ trung gian.
Kiến trúc này đặc biệt phù hợp với các cấu trúc dữ liệu có kích thước cố định
như trạng thái nhân vật, dữ liệu vật lý, thông tin thực thể trong game hoặc
các gói dữ liệu truyền tải qua mạng. Dữ liệu có thể được đọc và ghi với độ trễ
rất thấp trong khi vẫn duy trì tính liên tục của bộ nhớ.
Khác với Buffer-Model hướng tới dữ liệu động và khả năng thay đổi kích thước
trường dữ liệu, Buffer-View tập trung vào hiệu năng truy cập tối đa đối với
dữ liệu tĩnh. Mỗi trường được ánh xạ trực tiếp tới offset cố định trong Buffer,
cho phép CPU truy cập hiệu quả và tận dụng tốt tính locality của bộ nhớ.
Kết quả là Buffer-View giữ được các đặc điểm cốt lõi của Data-Oriented Design:
dữ liệu liên tục, không parse, không serialize trung gian, không cấp phát bổ sung,
đồng thời vẫn cung cấp trải nghiệm truy cập gần giống với việc thao tác trên
struct thông thường trong C#.
File 3 — Benchmark Buffer-View Initialization
Tệp này được sử dụng để kiểm chứng chi phí khởi tạo và ánh xạ cấu trúc dữ liệu
trên Buffer thông qua hai cơ chế InitStruct<T>() và
AsStruct<T>(). Mục tiêu là đánh giá khả năng chuyển đổi
giữa vùng nhớ thô và cấu trúc dữ liệu có kiểu mà không cần tạo đối tượng trung gian.
Hai cấu trúc thử nghiệm được sử dụng gồm Player và
Monster. Trong đó Monster chứa một đối tượng
Player cùng nhiều cấu trúc lồng nhau như
Position và Stats. Điều này giúp đánh giá khả năng
xử lý dữ liệu phân cấp trong một vùng nhớ liên tục.
Phương thức InitStruct<T>() có nhiệm vụ chuẩn bị Buffer
và ánh xạ vùng nhớ hiện tại thành một cấu trúc dữ liệu cụ thể. Sau khi khởi tạo,
toàn bộ trường dữ liệu của struct có thể được truy cập trực tiếp thông qua
tham chiếu mà không cần sao chép dữ liệu.
Phương thức AsStruct<T>() thực hiện việc diễn giải
vùng nhớ hiện có thành một struct tương ứng. Cơ chế này cho phép bên nhận
đọc dữ liệu trực tiếp từ Buffer mà không cần deserialize hoặc tái tạo đối tượng.
Tất cả các cấu trúc trong ví dụ đều sử dụng
[StructLayout(LayoutKind.Sequential, Pack = 1)]
nhằm đảm bảo bố cục bộ nhớ xác định và nhất quán giữa dữ liệu lưu trong Buffer
và dữ liệu được ánh xạ thành struct.
Kết quả của bài kiểm thử này được sử dụng làm cơ sở đánh giá chi phí nền
của Buffer-View trước khi tiến hành các phép đo truy cập, đọc/ghi dữ liệu
và so sánh với các phương pháp quản lý dữ liệu truyền thống ở các phần tiếp theo.
File 4 — Benchmark Buffer-View Read / Write
Tệp này được sử dụng để đánh giá chi phí truy cập dữ liệu của Buffer-View
trong quá trình đọc (Getter) và ghi (Setter). Khác với bài kiểm thử khởi tạo
ở phần trước, nội dung tập trung vào các thao tác xảy ra thường xuyên trong
quá trình vận hành thực tế của ứng dụng.
Dữ liệu thử nghiệm sử dụng cấu trúc Monster gồm nhiều cấp lồng nhau,
bao gồm Player, Position và Stats.
Điều này giúp mô phỏng các mô hình dữ liệu thực tế thường gặp trong game,
mô phỏng hoặc hệ thống xử lý trạng thái đối tượng.
Bài kiểm thử Setter thực hiện cập nhật toàn bộ các trường dữ liệu từ cấp gốc
đến các cấp con bên trong cấu trúc. Mục tiêu là xác minh rằng việc thay đổi
dữ liệu diễn ra trực tiếp trên vùng nhớ Buffer mà không cần tạo thêm đối tượng
hoặc sao chép dữ liệu trung gian.
Bài kiểm thử Getter thực hiện truy xuất các trường dữ liệu ở nhiều cấp độ khác nhau.
Toàn bộ dữ liệu được đọc trực tiếp từ Buffer thông qua tham chiếu struct đã được
ánh xạ trước đó, bảo đảm không phát sinh quá trình deserialize hoặc chuyển đổi kiểu.
Nhờ sử dụng cấu trúc bộ nhớ liên tục và ánh xạ trực tiếp bằng
AsStruct<T>(), việc truy cập dữ liệu trong Buffer-View
có thể được thực hiện tương tự như thao tác trên một struct thông thường,
trong khi vẫn giữ nguyên dữ liệu dưới dạng Buffer để phục vụ truyền tải
hoặc chia sẻ giữa các thành phần khác của hệ thống.
Kết quả thu được từ bài kiểm thử này sẽ được sử dụng ở phần đánh giá thực nghiệm
nhằm phân tích chi phí đọc và ghi dữ liệu của Buffer-View, đồng thời làm cơ sở
so sánh với các phương pháp biểu diễn dữ liệu truyền thống.
File 5 — Benchmark Buffer-Model Getter
Tệp này được sử dụng để đánh giá chi phí truy xuất dữ liệu của Buffer-Model
trên nhiều cấp độ lồng nhau khác nhau. Mục tiêu là xác minh rằng mô hình dữ liệu
dạng phân cấp vẫn có thể được truy cập thuận tiện như OOP truyền thống trong khi
dữ liệu thực tế vẫn được lưu trữ tập trung bên trong Buffer.
Cấu trúc thử nghiệm bao gồm ba tầng dữ liệu. Tầng gốc (F0) chứa thông tin người dùng,
tầng thứ hai (F1) chứa thông tin túi đồ và tầng thứ ba (F2) chứa thông tin vật phẩm.
Việc tổ chức theo nhiều cấp giúp mô phỏng các mô hình nghiệp vụ thường gặp trong
ứng dụng thực tế.
Các bài kiểm thử được chia thành nhiều nhóm khác nhau. Một số trường hợp chỉ truy cập
một trường đơn lẻ như Id hoặc Name, trong khi các trường hợp
khác đọc toàn bộ dữ liệu của một tầng nhằm đánh giá chi phí truy xuất khi số lượng
thuộc tính tăng lên.
Khác với Buffer-View sử dụng offset cố định của struct, Buffer-Model phải thực hiện
thêm bước xác định vị trí trường dữ liệu trong layout và điều hướng qua các model con.
Điều này tạo ra một lớp trừu tượng cao hơn, cho phép dữ liệu có thể thay đổi kích thước
động nhưng vẫn giữ được trải nghiệm truy cập quen thuộc thông qua thuộc tính.
Bài kiểm thử này tập trung đánh giá chi phí của cơ chế Getter trong Buffer-Model,
bao gồm việc truy cập dữ liệu nguyên thủy, dữ liệu chuỗi và dữ liệu nằm trong các
model lồng nhau. Đây là những thao tác xuất hiện thường xuyên trong quá trình đọc
trạng thái và xử lý nghiệp vụ của hệ thống.
Kết quả thu được sẽ được sử dụng ở phần thực nghiệm nhằm phân tích mức chi phí của
lớp trừu tượng Buffer-Model, đồng thời so sánh với Buffer-View và mô hình OOP
truyền thống trong các kịch bản truy cập dữ liệu nhiều cấp.
File 6 — Benchmark Buffer-Model Setter Simple
File này kiểm tra khả năng ghi dữ liệu (setter) của hệ thống Buffer-Model
trên cấu trúc dữ liệu lồng nhau nhiều cấp. Mô hình thử nghiệm gồm ba tầng:
User → Bag → Item, tương ứng với cấp gốc (F0), cấp con (F1) và cấp cháu (F2).
Mỗi thao tác gán thuộc tính như model.Id,
model.Bag.Name hoặc
model.Bag.Item.Description thực chất không ghi trực tiếp vào
object như OOP truyền thống. Thay vào đó, dữ liệu được ánh xạ vào một vùng
nhớ liên tục bên trong Buffer thông qua cơ chế LayoutModel.
Thử nghiệm sử dụng dữ liệu kích thước nhỏ và gần với các tình huống phổ biến
trong ứng dụng thực tế như số nguyên, chuỗi ngắn hoặc metadata. Mục tiêu là
đánh giá chi phí vận hành cơ bản của Buffer-Model trong điều kiện thông thường,
trước khi xem xét các trường hợp dữ liệu lớn ở các phần tiếp theo.
Kết quả cho thấy việc ghi dữ liệu vẫn giữ được độ ổn định khi truy cập qua
nhiều tầng lồng nhau. Chi phí phát sinh chủ yếu đến từ cơ chế định vị layout,
quản lý offset và đảm bảo tính linh hoạt của mô hình dữ liệu động, thay vì
chỉ đơn thuần là thao tác gán giá trị.
Điểm đáng chú ý là toàn bộ quá trình ghi dữ liệu không phát sinh cấp phát bộ
nhớ tạm thời (allocation) và không yêu cầu chuyển đổi dữ liệu trung gian.
Điều này giúp Buffer-Model duy trì đặc tính "ghi trực tiếp vào vùng nhớ đích",
tạo nền tảng cho các cơ chế truyền tải, đồng bộ hoặc lưu trữ dữ liệu ở các
tầng phía sau.
File benchmark này đóng vai trò đo lường chi phí setter cơ bản của
Buffer-Model. Phần phân tích chi tiết về hiệu năng và so sánh với các phương
pháp khác sẽ được trình bày ở mục kết quả thực nghiệm.
Buffer-Model Setter Heavy
File này mô phỏng các tình huống thường gặp trong hệ thống thực tế khi dữ liệu
không còn là các chuỗi ngắn hoặc giá trị nguyên đơn giản, mà là các khối dữ liệu
có kích thước lớn như hồ sơ người dùng, metadata, token xác thực hoặc nội dung mô tả.
Để đánh giá khả năng xử lý trong điều kiện áp lực cao, bài kiểm thử sử dụng các
mảng byte có kích thước từ 1 KB đến hơn 32 KB. Các dữ liệu này được ghi vào
nhiều cấp khác nhau của mô hình User → Bag → Item nhằm kiểm tra khả năng quản lý
offset, dịch chuyển dữ liệu và duy trì tính nhất quán của LayoutModel khi kích
thước trường thay đổi liên tục.
Song song với đó, một mô hình OOP truyền thống cũng được xây dựng để làm đối
tượng tham chiếu. Trong mô hình này, dữ liệu nhị phân phải được chuyển đổi sang
chuỗi thông qua Encoding.UTF8.GetString() trước khi lưu trữ, qua đó
phản ánh chi phí thường gặp trong các hệ thống xử lý dữ liệu động dựa trên object.
Mục tiêu chính của bài kiểm thử không phải đo tốc độ tuyệt đối của thao tác ghi,
mà là quan sát cách hệ thống phản ứng khi kích thước dữ liệu tăng lên hàng chục
hoặc hàng trăm lần. Đây là trường hợp mà các cơ chế quản lý bộ nhớ, cấp phát và
sao chép dữ liệu bắt đầu ảnh hưởng đáng kể đến hiệu năng tổng thể của ứng dụng.
Đối với Buffer-Model, dữ liệu được ghi trực tiếp dưới dạng nhị phân vào vùng nhớ
liên tục mà không cần chuyển đổi sang đối tượng trung gian. Nhờ đó, cấu trúc dữ
liệu vẫn giữ nguyên định dạng trong suốt quá trình lưu trữ và truyền tải, đồng
thời hạn chế tối đa các chi phí phát sinh từ việc tạo mới đối tượng hoặc sao chép
dữ liệu không cần thiết.
Bài kiểm thử này đóng vai trò đánh giá khả năng mở rộng của LayoutModel khi xử lý
dữ liệu lớn. Các số liệu định lượng và phân tích chi tiết về mức độ tăng trưởng
chi phí giữa Buffer-Model và OOP sẽ được trình bày trong phần kết quả thực nghiệm.
Buffer-Model Parse — Mô Hình Không Cần Serialize / Deserialize
File này đánh giá một trong những khác biệt cốt lõi giữa Buffer-Model và
mô hình hướng đối tượng truyền thống: khả năng chia sẻ dữ liệu mà không cần
thực hiện quá trình serialize hoặc deserialize.
Trong kịch bản OOP, đối tượng được chuyển thành chuỗi JSON thông qua quá trình
serialize, sau đó phải tiếp tục deserialize để tái tạo lại toàn bộ cây đối tượng.
Quá trình này đòi hỏi việc duyệt dữ liệu, phân tích cú pháp, tạo object mới,
cấp phát bộ nhớ và sao chép dữ liệu nhiều lần trước khi phía nhận có thể sử dụng.
Ngược lại, Buffer-Model lưu toàn bộ dữ liệu dưới dạng layout phẳng bên trong
một vùng nhớ liên tục. Khi cần truyền hoặc chia sẻ dữ liệu, phía nhận chỉ cần
gắn (attach) mô hình vào cùng vùng Buffer hiện có là có thể truy cập ngay toàn
bộ cấu trúc dữ liệu mà không cần tái tạo object hoặc chuyển đổi định dạng.
Cách tiếp cận này biến quá trình "parse dữ liệu" thành thao tác ánh xạ cấu trúc
lên vùng nhớ đã tồn tại. Dữ liệu vật lý không thay đổi, không bị sao chép và
cũng không cần đi qua các bước phân tích cú pháp trung gian như JSON, XML hoặc
các định dạng văn bản khác.
Kịch bản thử nghiệm sử dụng cùng một tập dữ liệu lớn gồm nhiều trường văn bản
và cấu trúc lồng nhau nhằm mô phỏng các tình huống thường gặp trong truyền dữ
liệu mạng, lưu trữ cache, chia sẻ trạng thái giữa các module hoặc đồng bộ dữ liệu
giữa các tiến trình.
Đây cũng là nền tảng cho triết lý "No Parse, No Alloc" của Buffer-Model:
dữ liệu chỉ được tạo một lần, sau đó có thể được truy cập hoặc chia sẻ trực tiếp
thông qua layout đã được định nghĩa trước. Phần kết quả thực nghiệm sẽ phân tích
chi tiết tác động của cách tiếp cận này đối với thời gian xử lý và chi phí bộ nhớ.