Cho Phép Tính Toán Trong Quá Trình Biên Dịch Của Lua - nói dối e blog

Cho Phép Tính Toán Trong Quá Trình Biên Dịch Của Lua

Lua là một ngôn ngữ lập trình có nhiều kỹ thuật tinh vi, khi sử dụng cần cân nhắc kỹ lưỡng. Trong mã nguồn của dự án Kepler, bạn sẽ thấy kỹ thuật biên dịch lặp lại được áp dụng rất sáng tạo, thậm chí có trường hợp biên dịch nhiều lần liên tiếp. Cụ thể, khi tải mã nguồn lần đầu, một đoạn chương trình Lua sẽ được thực thi để tạo ra mã nguồn thực sự cần thiết, sau đó biên dịch đoạn mã này.

Với tốc độ biên dịch cực nhanh của Lua, quá trình biên dịch lặp này chỉ diễn ra duy nhất một lần khi chương trình khởi động, từ đó mang lại hiệu suất cao. Những tham số có thể xác định ngay khi khởi tạo hệ thống (như dữ liệu đọc từ tệp cấu hình) sẽ được biên dịch trực tiếp thành hằng số trong chương trình.

Dưới đây là một đoạn mã nhỏ do chuyên gia Van Phong phát triển nhằm đơn giản hóa quy trình biên dịch lặp này, như một thú vui cuối tuần của ông:

Ví dụ 1: Tạo hằng số từ cấu hình

1
2
3
4
5
function foo()
  for i=1,| config "max" | do
    execute(i)
  end
end

Trong ví dụ này, giá trị kết thúc vòng lặp được lấy từ hàm config "max". Nếu mỗi lần chạy đều truy vấn cấu hình sẽ gây lãng phí tài nguyên. Với kỹ thuật này, giá trị chỉ cần được lấy duy nhất một lần khi tải chương trình. Ký hiệu | ... | cho biết đoạn mã bên trong sẽ được thực thi khi tải chương trình, tương tự như kỹ thuật template trong PHP.

Ví dụ 2: Biến template

1
2
3
4
| ALPHA = math.pi / 4 |
function foo(a)
  return a * math.sin(|ALPHA|)
end

Biến ALPHA được gán giá trị π/4 trong giai đoạn tiền xử lý. Kết quả 0.78539816339745 sẽ được biên dịch trực tiếp vào mã thực thi, trong khi ALPHA chỉ tồn tại trong quá trình tiền xử lý.

Ví dụ 3: Hàm unpack nâng cao

1
2
3
4
5
6
7
function unpackex(tbl,args)
  local ret={}
  for _,v in ipairs(args) do
    table.insert(ret,tbl[v])
  end
  return unpack(ret)
end

Sử dụng hàm này:

1
print ( unpackex( { one=1,two=2,three=3 }, { "one","two","three" } ))

Đoạn mã trên sẽ in ra 1, 2, 3 theo thứ tự quy định trong mảng tham số. Tuy nhiên, nếu gọi hàm như sau:

1
2
3
function foo(tbl)
  return unpackex(tbl,{"one","two","three"})
end

Mảng {"one","two","three"} sẽ được tạo mới mỗi lần gọi hàm, gây lãng phí bộ nhớ. Với kỹ thuật template, ta có thể tối ưu như sau:

1
2
3
function foo(tbl)
  return unpackex(tbl,| {"one","two","three"} |)
end

Cơ chế hoạt động của template

  • Khi gặp đoạn mã trong |...|, hệ thống sẽ thực thi ngay và chèn kết quả vào mã nguồn
  • Với kiểu dữ liệu đơn giản (nil, boolean, number, string), giá trị sẽ được chèn trực tiếp
  • Với kiểu phức tạp, hệ thống tạo biến local để lưu trữ
  • Đặc biệt, với cú pháp |# ... |, kết quả phải là chuỗi và sẽ được chèn nguyên vẹn vào mã nguồn

Ví dụ 4: Tính năng include

1
2
3
4
5
6
function include(filename)
  local f=assert(io.open(filename))
  local ret=f:read "*a"
  f:close()
  return ret
end

Sử dụng:

1
|# include "local.lua"|

Tệp local.lua sẽ được chèn trực tiếp vào mã nguồn như thể bạn viết thủ công tại vị trí đó.

Kỹ thuật này đặc biệt hữu ích để tối ưu hiệu suất bằng cách:

  1. Biến các giá trị cấu hình thành hằng số
  2. Tránh tạo đối tượng lặp đi lặp lại
  3. Tự động chèn mã tiền xử lý

Toàn bộ cơ chế có thể được triển khai dễ dàng bằng chính Lua. Bạn có thể tham khảo mã nguồn đầy đủ tại đây (liên kết giả định).

0%