Mô hình đánh giá song song và 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à nút.SkyValues
là các đối tượng không thể thay đổi, chứa tất cả dữ liệu được tạo trong quá trình tạo và các đầ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 gọn không thay đổi để tham chiếu đến mộtSkyValue
, ví dụ:FILECONTENTS:/tmp/foo
hoặcPACKAGE://foo
.SkyFunction
. Tạo các nút dựa trên khoá và các nút phụ thuộc của chúng.- 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 bản dựng.
Trước tiên, Bazel tìm SkyFunction
tương ứng với khoá của SkyKey
ở cấp cao nhất. Sau đó, hàm này yêu cầu đánh giá các nút cần thiết để đá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 được các nút lá. Các nút lá thường là những nút đại diện cho tệp đầu vào trong hệ thống tệp. Cuối cùng, Bazel sẽ có giá trị của SkyValue
cấp cao nhất, 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 trình về 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ượt nếu không thể biết trước tất cả các nút cần thiết để 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ại là một đường liên kết tượng trưng: hàm này cố gắng đọc tệp, nhận ra rằng đó là một đường 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 đích của đường liên kết tượng trưng. Nhưng bản thân hàm đó có thể là một symlink, trong trường hợp này, hàm ban đầu cũng sẽ cần tìm nạp mục tiêu của nó.
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 SkyFunction.Environment
cung cấp cho giao diện này. Sau đây là những việc mà các 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 có sẵn, giá trị của nút sẽ được trả về, nếu không,null
sẽ được trả về và bản thân hàm dự kiến sẽ trả vềnull
. Trong trường hợp thứ hai, 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.getValue
tương tự sẽ trả về một 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()
. Về cơ bản, thao tác này cũng 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ụ, chẳng hạn như ghi tệp vào hệ thống tệp. Bạn cần lưu ý rằng hai hàm khác nhau không được chồng chéo lên nhau. Nói chung, các tác dụng phụ ghi (nơi dữ liệu truyền ra ngoài từ Bazel) là ổn, các tác dụng phụ đọc (nơi dữ liệu truyền vào Bazel mà không có một phần phụ thuộc đã đăng ký) thì không, vì chúng là một 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 triển khai SkyFunction
hoạt động đúng cách sẽ tránh truy cập vào 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 đó khiến Bazel không đăng ký phần phụ thuộc dữ liệu trên tệp đã được đọ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ề một giá trị không phải null
cho biết đã hoàn tất.
Chiến lược đánh giá này có một số lợi ích:
- Độ 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, 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 sẽ giống nhau. Nếu tất cả các hàm bầu trời đều có tính xác định, thì điều này có nghĩa là toàn bộ bản dựng cũng sẽ có tính xác định.
- Mức 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 đều được ghi lại, Bazel chỉ có thể làm mất hiệu lực chính xác tập hợp các nút cần được 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 vào 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ể tạo một biểu đồ luồng dữ liệu hoàn chỉnh từ các tệp đầu vào đến các tệp đầu ra và sử dụng thông tin này để chỉ tạo lại những nút thực sự cần được tạo 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ể áp dụng: chiến lược từ dưới lên và chiến lược từ trên xuống. Lựa chọn nào là tối ưu còn tuỳ thuộc vào biểu đồ phần phụ thuộc.
Trong quá trình vô hiệu hoá từ dưới lên, sau khi biểu đồ được tạo và tập hợp các đầu vào đã thay đổi được xác định, tất cả các nút đều bị vô hiệu hoá, tuỳ thuộc vào các tệp đã thay đổi. Đây là lựa chọn tối ưu nếu cùng một nút cấp cao nhất sẽ được tạo lại. Xin lưu ý rằng quá trình vô hiệu hoá từ dưới lên yêu cầu chạy
stat()
trên tất cả các tệp đầu vào của bản dựng trước đó để xác định xem các tệp đó 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ụnginotify
hoặc một cơ chế tương tự để tìm hiểu về các tệp đã thay đổi.Trong quá trình vô hiệu hoá 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 hợp lệ mới được giữ lại. Điều này sẽ hiệu quả 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 đồ đó: quá trình vô hiệu hoá từ dưới lên sẽ vô hiệu hoá biểu đồ lớn hơn của bản dựng đầu tiên, không giống như quá trình vô hiệu hoá từ trên xuống, chỉ cần đi qua biểu đồ nhỏ của bản dựng thứ hai.
Bazel chỉ thực hiện quy trình vô hiệu hoá từ dưới lên.
Để tăng tính gia tăng hơn nữa, Bazel sử dụng tính năng cắt tỉa thay đổi: nếu một nút bị vô hiệu hoá, nhưng khi được tạo 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ị vô hiệu hoá do thay đổi trong nút này sẽ được "khôi phục".
Ví dụ: điều này sẽ hữu ích nếu bạn thay đổi một nhận xét trong tệp C++: thì tệp .o
được tạo từ tệp đó sẽ giống nhau, do đó, bạn không cần gọi trình liên kết nữa.
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 vô hiệu hoá một nút là một việc không thể tránh khỏi: khi một phần phụ thuộc thay đổi, nút phụ thuộc luôn được tạo lại từ đầu, ngay cả khi có một thuật toán tốt hơn sẽ làm thay đổi giá trị cũ của nút dựa trên các thay đổi. Một số ví dụ về trường hợp nên sử dụng tính năng này:
- 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ì tạo lại từ đầu.
Có hai lý do khiến Bazel không hỗ trợ những điều này theo cách có nguyên tắc:
- Hiệu suất chỉ tăng lên một chút.
- Khó xác thực rằng kết quả của quá trình đột biến giống với kết quả của quá trình tạo lại sạch 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ì bạn không cần phải thực hiện lại quy trình dexing.
Ánh xạ đến các khái niệm về Bazel
Đây là bản tóm tắt cấp cao về các hoạt động 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 dùng bởi mọi thứ 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. Tuỳ thuộc vào
FileStateValue
tương ứng và mọi đường liên kết tượng trưng cần được phân giải (chẳng hạn nhưFileValue
choa/b
cần đường dẫn đã phân giải củaa
và đường dẫn đã phân giải củaa/b
). Sự khác biệt giữaFileValue
vàFileStateValue
là rất quan trọng vì bạn có thể dùngFileStateValue
trong 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 của 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 dùng bởi mọi thứ quan tâm đến các mục của một thư mục. Phụ thuộc vào
DirectoryListingStateValue
tương ứng, cũng nhưFileValue
được liên kết của thư mục. - PackageValue. Biểu thị phiên bản đã phân tích cú pháp của tệp BUILD. Tuỳ thuộc vào
FileValue
của tệpBUILD
được liên kết, cũng như một cách gián tiếp vào mọiDirectoryListingValue
đượ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ệpBUILD
ở bên trong). - ConfiguredTargetValue. Biểu thị một mục tiêu đã định cấu hình, là một bộ gồm 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. Tuỳ thuộc vào
PackageValue
mà mục tiêu tương ứng nằm trong đó,ConfiguredTargetValues
củ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. Biểu thị một tệp trong bản dựng, có thể là một nguồn hoặc một 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 các tệp trong quá trình thực thi thực tế của các bước xây dựng. Tệp nguồn phụ thuộc vào
FileValue
của nút được liên kết và các cấu phần phần mềm đầu ra phụ thuộc vàoActionExecutionValue
củ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 hành động. Tuỳ thuộc vào
ArtifactValues
của các tệp đầu vào. Hành động mà nó thực thi nằm 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ằngActionExecutionValue
vàArtifactValue
sẽ không được dùng nếu giai đoạn thực thi không chạy.
Để minh hoạ, sơ đồ này cho thấy mối quan hệ giữa các phương thức triển khai SkyFunction sau khi tạo Bazel: