Mô hình đánh giá song song và mức độ gia tăng của Bazel.
Mô hình dữ liệu
Mô hình dữ liệu bao gồm các mục sau:
SkyValue. Còn được gọi là các nút.SkyValueslà các đối tượng không thay đổi chứa tất cả dữ liệu được tạo trong quá trình xây dựng và dữ liệu đầu vào của bản dựng. Ví dụ: tệp đầu vào, tệp đầu ra, mục tiêu và mục tiêu đã định cấu hình.SkyKey. Tên ngắn không thay đổi để tham chiếu đếnSkyValue, ví dụ:FILECONTENTS:/tmp/foohoặcPACKAGE://foo.SkyFunction. Xây dựng các nút dựa trên khoá và các nút phụ thuộc.- Biểu đồ nút. Một cấu trúc dữ liệu chứa mối quan hệ phụ thuộc giữa các nút.
Skyframe. Tên mã cho khung đánh giá gia tăng mà Bazel dựa trên.
Đánh giá
Bản dựng được tạo bằng cách đánh giá nút đại diện cho yêu cầu xây dựng.
Trước tiên, Bazel tìm SkyFunction tương ứng với khoá của cấp cao nhất
SkyKey. Sau đó, hàm yêu cầu đánh giá các nút cần để
đánh giá nút cấp cao nhất, từ đó dẫn đến các lệnh gọi SkyFunction khác,
cho đến khi đạt đến các nút lá. Các nút lá thường là các nút đại diện cho
tệp đầu vào trong hệ thống tệp. Cuối cùng, Bazel kết thúc bằng giá trị của
cấp cao nhất SkyValue, một số tác dụng phụ (chẳng hạn như tệp đầu ra trong hệ thống
tệp) và một biểu đồ có hướng không có chu kỳ của các phần phụ thuộc giữa các nút
liên quan đến bản dựng.
Một SkyFunction có thể yêu cầu SkyKeys trong nhiều lần truyền nếu không thể biết trước tất cả các nút cần để thực hiện công việc. Một ví dụ đơn giản là đánh giá
một nút tệp đầu vào hoá ra là một liên kết tượng trưng: hàm cố gắng đọc
tệp, nhận ra đó là một liên kết tượng trưng và do đó, tìm nạp nút hệ thống tệp
đại diện cho mục tiêu của liên kết tượng trưng. Tuy nhiên, bản thân liên kết đó có thể là một liên kết tượng trưng, trong
trường hợp đó, hàm ban đầu cũng cần tìm nạp mục tiêu của liên kết đó.
Các hàm được biểu thị trong mã bằng giao diện SkyFunction và các
dịch vụ do giao diện có tên là SkyFunction.Environment cung cấp cho hàm. Đây
là những việc mà hàm có thể làm:
- Yêu cầu đánh giá một nút khác bằng cách gọi
env.getValue. Nếu nút có sẵn, giá trị của nút sẽ được trả về, nếu không,nullsẽ được trả về và bản thân hàm dự kiến sẽ trả vềnull. Trong trường hợp sau, nút phụ thuộc sẽ được đánh giá, sau đó, trình tạo nút ban đầu sẽ được gọi lại, nhưng lần này, lệnh gọienv.getValuetương tự sẽ trả về giá trị không phải lànull - Yêu cầu đánh giá nhiều nút khác bằng cách gọi
env.getValues(). Lệnh này về cơ bản thực hiện tương tự, ngoại trừ việc các nút phụ thuộc được đánh giá song song. - Thực hiện tính toán trong quá trình gọi
- Có tác dụng phụ, ví dụ: ghi tệp vào hệ thống tệp. Cần phải cẩn thận để hai hàm khác nhau tránh can thiệp vào nhau. Nói chung, tác dụng phụ ghi (nơi dữ liệu truyền ra ngoài từ Bazel) là ổn, tác dụng phụ đọc (nơi dữ liệu truyền vào Bazel mà không có phần phụ thuộc đã đăng ký) thì không, vì chúng là phần phụ thuộc chưa đăng ký và do đó, có thể gây ra các bản dựng gia tăng không chính xác.
Các phương thức triển khai SkyFunction hoạt động tốt tránh truy cập dữ liệu theo bất kỳ cách nào khác
ngoài việc yêu cầu các phần phụ thuộc (chẳng hạn như bằng cách đọc trực tiếp hệ thống tệp),
vì điều đó dẫn đến việc Bazel không đăng ký phần phụ thuộc dữ liệu trên tệp
đã đọc, do đó dẫn đến các bản dựng gia tăng không chính xác.
Sau khi có đủ dữ liệu để thực hiện công việc, hàm sẽ trả về giá trị không phải là null
cho biết đã hoàn tất.
Chiến lược đánh giá này có một số lợi ích:
- Tính khép kín. Nếu các hàm chỉ yêu cầu dữ liệu đầu vào bằng cách phụ thuộc vào các nút khác, thì Bazel có thể đảm bảo rằng nếu trạng thái đầu vào giống nhau, thì dữ liệu được trả về cũng giống nhau. Nếu tất cả các hàm sky đều mang tính xác định, điều này có nghĩa là toàn bộ bản dựng cũng sẽ mang tính xác định.
- Tính gia tăng chính xác và hoàn hảo. Nếu tất cả dữ liệu đầu vào của tất cả các hàm được ghi lại, thì Bazel chỉ có thể làm mất hiệu lực tập hợp chính xác các nút cần làm mất hiệu lực khi dữ liệu đầu vào thay đổi.
- Tính song song. Vì các hàm chỉ có thể tương tác với nhau bằng cách yêu cầu các phần phụ thuộc, nên các hàm không phụ thuộc vào nhau có thể chạy song song và Bazel có thể đảm bảo rằng kết quả giống như khi chúng chạy tuần tự.
Mức độ gia tăng
Vì các hàm chỉ có thể truy cập dữ liệu đầu vào bằng cách phụ thuộc vào các nút khác, nên Bazel có thể xây dựng một biểu đồ luồng dữ liệu hoàn chỉnh từ tệp đầu vào đến tệp đầu ra và sử dụng thông tin này để chỉ xây dựng lại những nút thực sự cần xây dựng lại: bao đóng bắc cầu ngược của tập hợp các tệp đầu vào đã thay đổi.
Cụ thể, có 2 chiến lược gia tăng có thể có: chiến lược từ dưới lên và chiến lược từ trên xuống. Chiến lược nào là tối ưu còn tuỳ thuộc vào hình dạng của biểu đồ phần phụ thuộc.
Trong quá trình làm mất hiệu lực từ dưới lên, sau khi xây dựng biểu đồ và biết tập hợp các dữ liệu đầu vào đã thay đổi, tất cả các nút đều bị làm mất hiệu lực và phụ thuộc bắc cầu vào các tệp đã thay đổi. Đây là chiến lược tối ưu nếu cùng một nút cấp cao nhất sẽ được xây dựng lại. Xin lưu ý rằng việc làm mất hiệu lực từ dưới lên yêu cầu chạy
stat()trên tất cả tệp đầu vào của bản dựng trước đó để xác định xem chúng có bị thay đổi hay không. Bạn có thể cải thiện điều này bằng cách sử dụnginotifyhoặc một cơ chế tương tự để tìm hiểu về các tệp đã thay đổi.Trong quá trình làm mất hiệu lực từ trên xuống, bao đóng bắc cầu của nút cấp cao nhất sẽ được kiểm tra và chỉ những nút có bao đóng bắc cầu sạch mới được giữ lại. Chiến lược này sẽ tốt hơn nếu biểu đồ nút lớn, nhưng bản dựng tiếp theo chỉ cần một tập hợp con nhỏ của biểu đồ đó: việc làm mất hiệu lực từ dưới lên sẽ làm mất hiệu lực biểu đồ lớn hơn của bản dựng đầu tiên, không giống như việc làm mất hiệu lực từ trên xuống, chỉ cần đi qua biểu đồ nhỏ của bản dựng thứ hai.
Bazel chỉ làm mất hiệu lực từ dưới lên.
Để tăng thêm mức độ gia tăng, Bazel sử dụng cắt tỉa thay đổi: nếu một nút bị làm mất hiệu lực, nhưng khi xây dựng lại, hệ thống phát hiện thấy giá trị mới của nút đó giống với giá trị cũ, thì các nút bị làm mất hiệu lực do thay đổi trong nút này sẽ được "khôi phục".
Ví dụ: điều này hữu ích nếu một người thay đổi nhận xét trong tệp C++: thì tệp
.o được tạo từ đó sẽ giống nhau, do đó, không cần gọi
lại trình liên kết.
Liên kết / Biên dịch gia tăng
Hạn chế chính của mô hình này là việc làm mất hiệu lực của một nút là một vấn đề tất cả hoặc không có gì: khi một phần phụ thuộc thay đổi, nút phụ thuộc luôn được xây dựng lại từ đầu, ngay cả khi có một thuật toán tốt hơn sẽ thay đổi giá trị cũ của nút dựa trên các thay đổi. Một vài ví dụ về trường hợp này sẽ hữu ích:
- Liên kết gia tăng
- Khi một tệp lớp đơn lẻ thay đổi trong tệp JAR, bạn có thể sửa đổi tệp JAR tại chỗ thay vì xây dựng lại từ đầu.
Lý do Bazel không hỗ trợ những điều này theo cách có nguyên tắc là do 2 lý do:
- Có những lợi ích về hiệu suất bị hạn chế.
- Khó xác thực rằng kết quả của quá trình thay đổi giống với kết quả của quá trình xây dựng lại sạch sẽ và Google coi trọng các bản dựng có thể lặp lại từng bit.
Cho đến nay, bạn có thể đạt được hiệu suất đủ tốt bằng cách phân tách một bước xây dựng tốn kém và đạt được quá trình đánh giá lại một phần theo cách đó. Ví dụ: trong một ứng dụng Android, bạn có thể chia tất cả các lớp thành nhiều nhóm và dex chúng riêng biệt. Bằng cách này, nếu các lớp trong một nhóm không thay đổi, thì không cần phải thực hiện lại quá trình dexing.
Liên kết với các khái niệm của Bazel
Đây là bản tóm tắt cấp cao về các phương thức triển khai SkyFunction và SkyValue
chính mà Bazel sử dụng để thực hiện bản dựng:
- FileStateValue. Kết quả của một
lstat(). Đối với các tệp hiện có, hàm này cũng tính toán thông tin bổ sung để phát hiện các thay đổi đối với tệp. Đây là nút cấp thấp nhất trong biểu đồ Skyframe và không có phần phụ thuộc. - FileValue. Được sử dụng bởi bất kỳ thành phần nào quan tâm đến nội dung thực tế hoặc
đường dẫn đã phân giải của một tệp. Phụ thuộc vào
FileStateValuevà mọi liên kết tượng trưng cần được phân giải (chẳng hạn nhưFileValuechoa/bcần đường dẫn đã phân giải củaavà đường dẫn đã phân giải củaa/b). Sự khác biệt giữaFileValuevàFileStateValuelà rất quan trọng vì bạn có thể sử dụngFileStateValuetrong trường hợp không thực sự cần nội dung của tệp. Ví dụ: nội dung tệp không liên quan khi đánh giá các glob hệ thống tệp (chẳng hạn nhưsrcs=glob(["*/*.java"])). - DirectoryListingStateValue. Kết quả của
readdir(). Giống nhưFileStateValue, đây là nút cấp thấp nhất và không có phần phụ thuộc. - DirectoryListingValue. Được sử dụng bởi bất kỳ thành phần nào quan tâm đến các mục của
một thư mục. Phụ thuộc vào
DirectoryListingStateValuetương ứng, cũng nhưFileValueđược liên kết của thư mục. - PackageValue. Đại diện cho phiên bản đã phân tích cú pháp của tệp BUILD. Phụ thuộc vào
FileValuecủa tệpBUILDđược liên kết, đồng thời phụ thuộc bắc cầu vào bất kỳDirectoryListingValuenào được dùng để phân giải các glob trong gói (cấu trúc dữ liệu đại diện cho nội dung của tệpBUILDbên trong). - ConfiguredTargetValue. Đại diện cho một mục tiêu đã định cấu hình, là một bộ
gồm tập hợp các thao tác được tạo trong quá trình phân tích một mục tiêu và
thông tin được cung cấp cho các mục tiêu đã định cấu hình phụ thuộc. Phụ thuộc vào
PackageValuemà mục tiêu tương ứng nằm trong đó,ConfiguredTargetValuescủa các phần phụ thuộc trực tiếp và một nút đặc biệt đại diện cho cấu hình bản dựng. - ArtifactValue. Đại diện cho một tệp trong bản dựng, có thể là nguồn hoặc cấu phần phần mềm đầu ra. Cấu phần phần mềm gần như tương đương với tệp và được dùng để
tham chiếu đến tệp trong quá trình thực thi thực tế các bước xây dựng. Tệp nguồn
phụ thuộc vào
FileValuecủa nút được liên kết và cấu phần phần mềm đầu ra phụ thuộc vàoActionExecutionValuecủa bất kỳ thao tác nào tạo ra cấu phần phần mềm. - ActionExecutionValue. Đại diện cho việc thực thi một thao tác. Phụ thuộc vào
ArtifactValuescủa các tệp đầu vào. Thao tác mà nó thực thi được chứa trong SkyKey, điều này trái ngược với khái niệm rằng SkyKey phải nhỏ. Xin lưu ý rằngActionExecutionValuevàArtifactValuekhông được sử dụng nếu giai đoạn thực thi không chạy.
Để hỗ trợ trực quan, sơ đồ này cho thấy mối quan hệ giữa các phương thức triển khai SkyFunction sau khi xây dựng chính Bazel:
