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à nút.SkyValues
là các đối tượng bất biến chứa tất cả dữ liệu được xây dựng trong quá trình tạo bản 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à tệp đã định cấu hình mục tiêu.SkyKey
. Một tên ngắn bất biến để tham chiếu đếnSkyValue
, 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.- Biểu đồ nút. Cấu trúc dữ liệu chứa mối quan hệ phụ thuộc giữa nút.
Skyframe
. Tên mã cho khung đánh giá gia tăng Bazel là dựa trên.
Đánh giá
Một bản dựng sẽ đánh giá nút đại diện cho yêu cầu bản dựng (đây là trạng thái chúng ta đang hướng đến, nhưng vẫn còn rất nhiều mã cũ cần được thêm vào). Trước tiên, SkyFunction
của lớp đó được tìm thấy và gọi bằng khoá của SkyKey
cấp cao nhất. Sau đó, hàm này sẽ yêu cầu đánh giá các nút mà nó cần để đánh giá nút cấp cao nhất, từ đó dẫn đến các lệnh gọi hàm khác, v.v. cho đến khi đạt được 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, chúng ta sẽ đưa ra giá trị của SkyValue
cấp cao nhất, một số hiệu ứng phụ (chẳng hạn như các tệp đầu ra trong hệ thống tệp) và một đồ thị không chu trình có hướng của các phần phụ thuộc giữa các nút có liên quan đến bản dựng.
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 mà nó cần để thực hiện công việc của mình. Một ví dụ đơn giản là đánh giá nút tệp đầu vào nhưng lại là một đường liên kết tượng trưng: hàm này sẽ cố gắng đọc tệp, nhận ra rằng đó là một đường liên kết tượng trưng, từ đó tìm nạp nút hệ thống tệp đại diện cho mục tiêu của đường liên kết tượng trưng. Tuy nhiên, bản thân điều đó có thể là một đường liên kết tượng trưng, trong trường hợp đó, hàm gốc cũng 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
còn các dịch vụ được cung cấp qua giao diện có tên là SkyFunction.Environment
. 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 nút có sẵn, giá trị của nút đó sẽ được trả về, nếu không,null
sẽ được trả về và 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 đượ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ảinull
. - Yêu cầu đánh giá nhiều nút khác bằng cách gọi
env.getValues()
. Về cơ bản, điều này giống nhau, ngoại trừ việc các nút phụ thuộc được đánh giá song song. - Thực hiện việc tính toán trong lệnh gọi
- Có tác dụng phụ, chẳng hạn như ghi tệp vào hệ thống tệp. Cần chú ý để đảm bảo hai chức năng khác nhau không giẫm đạp lẫn nhau. Nói chung, bạn có thể viết các hiệu ứng phụ (trong đó dữ liệu chuyển ra từ Bazel). Đọc các hiệu ứng phụ (trong đó 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.
Quá trình triển khai SkyFunction
không được 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 đó dẫn đến việc Bazel không đăng ký phần phụ thuộc dữ liệu trên tệp đã đọc, từ đó dẫn đến các bản dựng tăng dần không chính xác.
Khi có đủ dữ liệu để thực hiện công việc, hàm đó sẽ trả về 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:
- Tính bí mật. 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 là như nhau thì dữ liệu đó sẽ được trả về. Nếu tất cả các hàm bầu trời đều mang tính xác định, thì toàn bộ bản dựng cũng sẽ có tính xác định.
- Mức độ 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ể vô hiệu hoá chính tập hợp các nút cần được vô hiệu hoá khi dữ liệu đầu vào thay đổi.
- 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 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ả sẽ giống như khi chúng được 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ể xây dựng 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ỉ xây dựng lại các nút thực sự cần xây dựng lại: kết thúc bắc cầu ngược của tập hợp các tệp đầu vào đã thay đổi.
Cụ thể, có thể có hai chiến lược về mức độ gia tăng: chiến lược từ dưới lên và chiến lược từ trên xuống. Phương án nào tối ưu sẽ phụ thuộc vào hình thức của biểu đồ phần phụ thuộc.
Trong quá trình vô hiệu hoá từ dưới lên, sau khi tạo một biểu đồ và xác định được tập hợp các đầu vào đã thay đổi, tất cả các nút đều không hợp lệ và phụ thuộc bắc cầu vào các tệp đã thay đổi. Đây là phương pháp tối ưu nếu chúng ta biết rằng cùng một nút cấp cao nhất sẽ được tạo lại. Lưu ý rằng việc 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 chúng đã được thay đổi hay chưa. 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, đó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 đó mới được giữ lại khi đóng bắc cầu là không có lỗi. Sẽ tốt hơn nếu chúng ta biết rằng biểu đồ nút hiện tại lớn, nhưng chúng ta chỉ cần một tập hợp con nhỏ của biểu đồ này trong bản dựng tiếp theo: việc vô hiệu hoá 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 vô hiệu hoá từ trên xuống chỉ đi qua biểu đồ nhỏ của bản dựng thứ hai.
Hiện tại, chúng tôi chỉ thực hiện vô hiệu hoá từ dưới lên.
Để có mức độ gia tăng hơn nữa, chúng ta sử dụng phương pháp rút gọn thay đổi: nếu một nút không hợp lệ, nhưng khi tạo lại, hệ thống sẽ phát hiện ra rằng giá trị mới giống với giá trị cũ, các nút đã mất hiệu lực do thay đổi trong nút này sẽ được “được phục hồi lại”.
Cách này hữu ích, chẳng hạn như nếu một người thay đổi nhận xét trong tệp C++: thì tệp .o
được tạo từ tệp đó sẽ giống nhau, do đó, chúng ta không cần phải gọi lại trình liên kết.
Liên kết / Biên dịch tăng dần
Hạn chế chính của mô hình này là việc vô hiệu hoá nút là vấn đề hoàn toàn hoặc không có gì: 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 vài ví dụ về điều này hữu ích:
- Liên kết gia tăng
- Về mặt lý thuyết, khi một tệp
.class
thay đổi trong.jar
, chúng ta có thể sửa đổi tệp.jar
thay vì tạo lại tệp đó từ đầu.
Lý do mà Bazel hiện không hỗ trợ những việc này theo nguyên tắc (chúng tôi có một số biện pháp hỗ trợ cho việc liên kết gia tăng, nhưng không được triển khai trong Skyframe) có hai lần: chúng tôi chỉ đạt được hiệu suất hạn chế và khó đảm bảo rằng kết quả của quá trình đột biến cũng giống như việc xây dựng lại sạch sẽ, và Google đánh giá các bản dựng có thể lặp lại từng bit.
Cho đến nay, chúng ta luôn có thể đạt được hiệu suất đủ tốt bằng cách chỉ cần phân tách một bước xây dựng tốn kém và đạt được đánh giá lại một phần theo cách đó: công cụ này chia tất cả các lớp trong một ứng dụng thành nhiều nhóm và tạo tệp dex trên các lớp đó 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 làm lại quá trình tạo tệp dex.
Liên kết với các khái niệm Bazel
Đây là thông tin tổng quan sơ bộ về một số cách triển khai SkyFunction
mà Bazel sử dụng để tạo một bản dựng:
- FileStateValue. Kết quả của
lstat()
. Đối với các tệp hiện có, chúng tôi 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 cho những ai quan tâm đến nội dung thực tế và/hoặc đường dẫn đã phân giải của một tệp. Phụ 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 làa
và đường dẫn đã phân giải củaa/b
). Sự khác biệt giữaFileStateValue
là rất quan trọng vì trong một số trường hợp (ví dụ: đánh giá các lỗi trong hệ thống tệp (chẳng hạn nhưsrcs=glob(["*/*.java"])
), nội dung của tệp không thực sự cần thiết. - DirectoryListingValue. Về cơ bản, đây là kết quả của
readdir()
. Phụ thuộc vàoFileValue
được liên kết với thư mục. - PackageValue (Giá trị gói). Đạ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
FileValue
của tệpBUILD
được liên kết, đồng thời cũng theo cách bắc cầu bất kỳDirectoryListingValue
nào được dùng để phân giải các khối cầu trong gói (cấu trúc dữ liệu biểu thị nội dung của tệpBUILD
trong nội bộ) - ConfiguredTargetValue. Đại diện cho mục tiêu đã định cấu hình, là một bộ gồm tập hợp các hành động được tạo trong quá trình phân tích 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 vào mục tiêu này. Tuỳ thuộc vào
PackageValue
có mục tiêu tương ứng,ConfiguredTargetValues
của 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à nguồn hoặc cấu phần phần mềm đầu ra (các 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ác bước tạo bản dựng). Đối với các tệp nguồn, điều này phụ thuộc vào
FileValue
của nút liên kết. Đối với các cấu phần phần mềm đầu ra, điều này phụ thuộc vàoActionExecutionValue
của bất kỳ hành động nào tạo ra cấu phần phần mềm. - ActionExecutionValue. Biểu thị quá trình thực thi một hành động. Phụ thuộc vào
ArtifactValues
của các tệp đầu vào. Hành động mà hàm này thực thi hiện nằm trong khoá bầu trời, trái với khái niệm phím trời nên có kích thước nhỏ. Chúng tôi đang nỗ lực giải quyết sự không nhất quán này (lưu ý rằngActionExecutionValue
vàArtifactValue
sẽ không được dùng nếu chúng tôi không chạy giai đoạn thực thi trên Skyframe).