- Ngôn ngữ Go hầu như không có hành vi không được định nghĩa và có ngữ nghĩa GC (garbage collection) đơn giản
- Trong Go có thể thực hiện quản lý bộ nhớ thủ công, và điều này có thể được thực hiện theo cách phối hợp với GC
- Arena là một cấu trúc dữ liệu giúp cấp phát hiệu quả bộ nhớ có cùng vòng đời, và bài viết giải thích cách triển khai điều này trong Go
- Giải thích cách GC quản lý bộ nhớ thông qua thuật toán Mark and Sweep
- Có thể cải thiện hiệu năng cấp phát bộ nhớ bằng cách dùng Arena, và điều này khả thi thông qua nhiều tối ưu hóa khác nhau
- Cố gắng tăng hiệu năng và giảm tối đa gánh nặng cho GC thông qua loại bỏ write barrier, tái sử dụng bộ nhớ, chunk pooling v.v.
- Đưa ra các mẫu an toàn và nhanh khi xử lý bộ nhớ quy mô lớn trong thực tế thông qua các tính năng như triển khai
Realloc, tái sử dụng Arena và khởi tạo lại (Reset)
Tổng quan về cấp phát bộ nhớ thủ công dựa trên Arena trong Go
- Go là một ngôn ngữ an toàn nhờ hành vi GC rõ ràng và gần như không có Undefined Behavior
- Có thể tận dụng gói
unsafe để điều khiển trực tiếp bộ nhớ theo cách phù hợp với cách GC được triển khai bên trong
- Bài viết này giải thích cách tạo một trình cấp phát bộ nhớ dựa trên cấu trúc Arena có thể phối hợp với GC trong Go
Định nghĩa và sự cần thiết của Arena
- Arena là một cấu trúc để cấp phát hiệu quả các đối tượng có cùng vòng đời
- Nếu
append thông thường mở rộng mảng theo cấp số nhân, thì Arena thêm block mới và cung cấp con trỏ
- Giao diện tiêu chuẩn như sau:
Alloc(size, align uintptr) unsafe.Pointer
Con trỏ và cách GC hoạt động
- GC hoạt động theo cách đánh dấu (mark) và thu hồi (sweep) bộ nhớ
- Để có GC chính xác, nó sử dụng siêu dữ liệu gọi là pointer bits để cho biết vị trí của con trỏ
- Nếu xử lý con trỏ sai trong Arena, GC có thể không theo dõi được con trỏ, dẫn tới khả năng xảy ra lỗi Use-After-Free
Cách thiết kế Arena
- Cấu trúc Arena có các trường sau:
- Mọi cấp phát đều được xử lý với căn chỉnh 8 byte, và nếu không đủ thì tạo chunk mới bằng
nextPow2
- Chunk được cấp phát dưới dạng kiểu
struct { A [N]uintptr; P *Arena } thay vì []uintptr, để GC có thể theo dõi Arena
Cách đảm bảo an toàn con trỏ cho Arena
- Khi chỉ sử dụng các con trỏ được cấp phát bên trong Arena, GC sẽ giữ toàn bộ Arena tồn tại
- Thiết lập để con trỏ tham chiếu tới Arena, qua đó đảm bảo toàn bộ Arena sống sót dưới GC
- Phương thức cấp phát của Arena thực hiện như sau:
- lưu con trỏ Arena ở cuối chunk trong
allocChunk()
Kết quả benchmark hiệu năng
- So với
new mặc định, cấp phát bằng Arena cho thấy cải thiện hiệu năng trung bình hơn 2~4 lần
- Ngay cả trong tình huống tải GC lớn, cách làm bằng Arena vẫn cho hiệu năng tốt hơn tới hơn 2 lần
- Các tối ưu như loại bỏ write barrier, tận dụng
uintptr v.v. mang lại cải thiện hiệu năng tới 20% ở các cấp phát nhỏ
Chiến lược tái sử dụng chunk và loại bỏ Heap
- Có thể tái sử dụng chunk bằng
sync.Pool
- Thông qua
runtime.SetFinalizer(), khi Arena biến mất thì chunk được trả lại pool
- Hiệu năng cải thiện rất nhiều ở các cấp phát nhỏ, nhưng ở cấp phát lớn có thể chậm hơn
new
Chức năng khởi tạo lại và tái sử dụng Arena
- Có thể đưa Arena trở về trạng thái ban đầu bằng phương thức
Reset()
- Mức độ rủi ro cao, nhưng có thể tái sử dụng cùng cấu trúc mà không cần cấp phát lại bộ nhớ
- Ngay cả khi tái sử dụng, việc tái sử dụng chunk cũng giúp tăng hiệu năng đáng kể
Triển khai chức năng Realloc
- Triển khai chức năng
realloc trong Arena để mở rộng động cho lần cấp phát gần nhất
- Nếu không thể, sẽ cấp phát bộ nhớ mới rồi sao chép dữ liệu
Kết luận và cung cấp toàn bộ mã
- Bằng việc hiểu sâu cơ chế GC của Go và dựa trên cách triển khai nội bộ, bài viết hoàn thiện một bộ quản lý bộ nhớ dựa trên Arena
- Đây là một cấu trúc vừa an toàn vừa hiệu năng cao, và nếu dùng đúng cách thì rất hữu ích khi xử lý các cấu trúc dữ liệu quy mô lớn
- Mã triển khai đầy đủ bao gồm struct Arena và các thành phần
New, Alloc, Reset, allocChunk, finalize v.v.
1 bình luận
Ý kiến trên Hacker News
Bài này đọc khá thú vị
Reference[T]cung cấp cùng chức năngGần đây khi tinh chỉnh hiệu năng trong Go, tôi cũng đã dùng một thiết kế arena rất giống như vậy để đẩy hiệu năng lên mức tối đa
unsafeMột vài điểm có thể cải thiện dễ dàng
capmạnh tay hơn trước khi gọiappendtích hợp sẵnunsafe.Stringhữu ích để truyền chuỗi từ byte slice mà không cần cấp phátHơi ngoài chủ đề, nhưng tôi thích minimap ở bên cạnh
Tóm tắt cho những ai ngại đọc bài dài
unsafetrong Go để xây dựng một arena allocator nhằm tăng tốc công việc cấp phátunsafe.Pointer, GC sẽ không nhìn thấy đúng những gì được trỏ tới trong arena và có thể giải phóng nhầmchunk) trỏ đến mọi khối bộ nhớ lớn mà arena lấy từ hệ thốngreflect.StructOfđể tạo một kiểu mới có thêm các trường con trỏ vào những khối nàyLiên quan: thảo luận về việc thêm "vùng nhớ" vào thư viện chuẩn
Nội dung khá thú vị
Go ưu tiên việc không làm vỡ hệ sinh thái
Ghi chú meta ngắn