Phân Tích Mã Nguồn Lua GC (5) - nói dối e blog

Phân Tích Mã Nguồn Lua GC (5)

Hôm nay chúng ta hãy cùng tìm hiểu về write barrier – một thành phần quan trọng trong cơ chế thu gom rác của Lua. Trong quá trình quét bộ nhớ, do việc xử lý được chia thành nhiều giai đoạn, sẽ có những lúc các đối tượng vừa được đánh dấu thành màu đen nhưng lại bị sửa đổi lại – khiến chúng cần phải được đánh dấu lại. Để giải quyết vấn đề này, hệ thống phải thiết lập một rào chắn ghi (write barrier) mỗi khi có thao tác chỉnh sửa đối tượng. Những thay đổi này sẽ đảm bảo các đối tượng liên quan được đánh dấu màu chính xác, hoặc được ghi lại vào danh sách để xử lý ở giai đoạn atomic cuối cùng của quá trình đánh dấu.

Hệ thống có 4 macro API liên quan đến barrier, được định nghĩa tại dòng 86 của file lgc.h:

1
2
3
4
5
6
7
8
9
#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \
  luaC_barrierf(L,obj2gco(p),gcvalue(v)); }
#define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \
  luaC_barrierback(L,t); }
#define luaC_objbarrier(L,p,o) \
  { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \
    luaC_barrierf(L,obj2gco(p),obj2gco(o)); }
#define luaC_objbarriert(L,t,o) \
  { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); }
  • luaC_barrierluaC_objbarrier có chức năng tương tự nhau, nhưng khác nhau ở kiểu dữ liệu đầu vào:

    • luaC_barrier xử lý các giá trị kiểu TValue (giống như biến lưu trữ giá trị tổng quát trong Lua),
    • luaC_objbarrier xử lý các đối tượng thuộc lớp GCObject (đối tượng có thể bị thu gom rác).
      Cả hai đều dùng để thiết lập mối liên kết giữa vp khi v đang ở trạng thái trắng (white) và p đã là đen (black), từ đó gọi hàm luaC_barrierf.
  • luaC_barriertluaC_objbarriert thì được dùng khi thiết lập mối liên kết giữa vt, gọi đến luaC_barrierback nếu điều kiện tương tự (v trắng, t đen) thỏa mãn.

Tại sao bảng (table) được xử lý đặc biệt?

Bảng trong Lua có cấu trúc tham chiếu 1-đến-N, nghĩa là một bảng có thể chứa rất nhiều phần tử và thường xuyên bị chỉnh sửa. Trong khi đó, các kiểu dữ liệu khác như closure hay thread có mối liên kết hạn chế hơn. Việc xử lý riêng bảng giúp tối ưu hiệu năng của cơ chế barrier, giảm thiểu chi phí tính toán không cần thiết.

Chi tiết về luaC_barrierf

Hàm luaC_barrierf (định nghĩa tại dòng 663 của lgc.c) đảm nhiệm việc đánh dấu ngay lập tức các đối tượng mới thiết lập mối liên kết. Mã nguồn của nó như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v) {
  global_State *g = G(L);
  lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
  lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause);
  lua_assert(ttype(&o->gch) != LUA_TTABLE);
  if (g->gcstate == GCSpropagate)
    reallymarkobject(g, v);
  else
    makewhite(g, o);
}

Tóm lại:

  • Nếu GC đang ở trạng thái GCSpropagate (giai đoạn đánh dấu), v sẽ được đánh dấu bằng hàm reallymarkobject.
  • Ngược lại, đối tượng o sẽ bị chuyển về màu trắng để tránh các barrier gọi lặp lại trong tương lai. Kỹ thuật này tận dụng cơ chế “hai màu trắng” (double-white) của Lua – giúp giảm thiểu số lần gọi barrier do o không còn là màu đen.

Vai trò của luaC_barrierback

Hàm luaC_barrierback (dòng 676 lgc.c) đưa các bảng bị chỉnh sửa trở lại danh sách grayagain, để xử lý ở giai đoạn atomic:

1
2
3
4
5
6
7
void luaC_barrierback (lua_State *L, Table *t) {
  global_State *g = G(L);
  GCObject *o = obj2gco(t);
  black2gray(o);
  t->gclist = g->grayagain;
  g->grayagain = o;
}

Danh sách grayagain sẽ được xử lý toàn bộ ở giai đoạn atomic. Việc chuyển bảng về màu xám (gray) giúp ngăn chặn các lần gọi liên tiếp luaC_barrierback khi bảng tiếp tục bị sửa đổi.

Xử lý các bảng yếu (weak table)

Các bảng yếu không bao giờ gọi luaC_barrierback vì hai lý do:

  1. Bảng yếu không thể tồn tại ở trạng thái đen.
  2. Mỗi GCObject chỉ có thể nằm trong một danh sách duy nhất. Bảng yếu đã được liên kết vào danh sách weak nên không thể thêm vào grayagain.

Đoạn mã tại dòng 285 lgc.c giải thích điều này:

1
2
if (traversetable(g, h)) /* Bảng có yếu không? */
  black2gray(o); /* Giữ nguyên màu xám */

Tại sao phải đánh dấu lại (remark) các bảng yếu?

Bảng yếu có thể là key-weak, value-weak, hoặc cả hai. Trong quá trình quét, các sửa đổi trên bảng yếu không kích hoạt barrier, có thể khiến một số mối quan hệ “mạnh” (strong) bị ẩn trong bảng. Do đó,

0%