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 đạt được 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 cấp cao nhất
SkyKey
. Sau đó, hàm này 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 SkyFunction
khác,
cho đến khi đạt được các nút lá. Nút lá thường là những nút đại diện cho
đầu vào trong hệ thống tệp. Cuối cùng, Bazel kết thúc với 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 tệp
hệ) và đồ thị không chu trình có hướng của các phần phụ thuộc giữa các nút
liên quan đến quá trình tạo bản dựng.
SkyFunction
có thể yêu cầu SkyKeys
trong nhiều lần truyền nếu không thể xác định trong
phát triển 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 hoá ra là một 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 liên kết tượng trưng và do đó tìm nạp nút hệ thống tệp
thể hiện mục tiêu của đường liên kết tượng trưng. Nhưng bản thân nó có thể là một liên kết tượng trưng, theo
trường hợp nào thì 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ởi giao diện SkyFunction
và
các dịch vụ được cung cấp qua giao diện có tên là SkyFunction.Environment
. Các
là những chức năng 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 đang hoạt động thì giá trị của nút đó sẽ được trả về, nếu không thìnull
sẽ được trả về và hàm này dự kiến sẽ trả vềnull
. Trong trường hợp sau, nút phụ thuộc được đánh giá, sau đó trình tạo nút ban đầu là đã gọi lại nhưng lần này, cùng một lệnh gọienv.getValue
sẽ trả về một giá trị 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()
. Điều này về cơ bản 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. Nhu cầu chăm sóc để hai chức năng khác nhau tránh dẫm lên nhau ngón chân. Nói chung, hãy viết các hiệu ứng phụ (trong đó dữ liệu di chuyển ra từ Bazel) được rồi, hãy đọ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ể khiến các bản dựng gia tăng không chính xác.
Những cách triển khai SkyFunction
hoạt động đúng cách tránh truy cập dữ liệu theo bất kỳ cách nào khác
so vớ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 này
do đó dẫn đến các bản dựng tăng dần không chính xác.
Khi một hàm có đủ dữ liệu để thực hiện công việc của mình, hàm đó sẽ trả về một giá trị không phải là null
giá trị cho biết hoàn thành.
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, Bazel có thể đảm bảo rằng nếu trạng thái đầu vào là như nhau, cùng một dữ liệu sẽ được trả về. Nếu tất cả các hàm bầu trời đều mang tính tất định, điều này có nghĩa là toàn bộ bản dựng cũng sẽ mang tính quyết đị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 xác một tập hợp các nút cần không có hiệu lực 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 các phần phụ thuộc yêu cầu, các hàm không phụ thuộc lẫn nhau có thể chạy song song và Bazel có thể đảm bảo rằng kết quả sẽ giống như chúng được 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 đầu ra tệp và sử dụng thông tin này để chỉ tạo lại các nút thực sự cần được tạo lại: đó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ó thể có hai chiến lược mức độ gia tăng: chiến lược từ dưới lên và danh sách từ trên xuống. Mục nào là tối ưu phụ thuộc vào cách biểu đồ phần phụ thuộc trông như thế nào.
Trong quá trình vô hiệu hoá từ dưới lên, sau khi tạo biểu đồ và tập hợp giá trị thay đổi đầu vào đã biết, tất cả các nút đều không hợp lệ và phụ thuộc bắc cầu vào tệp bị thay đổi. Đây là giá trị tối ưu nếu bạn xây dựng cùng một nút cấp cao nhất một lần nữa. 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ả 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. Chiến dịch này có thể cải thiện được bằng cách sử dụnginotify
hoặc một cơ chế tương tự để tìm hiểu tệp bị 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 được chọn và chỉ những nút đó được giữ lại có đóng bắc cầu là sạch. Phương pháp này phù hợp hơn nếu biểu đồ nút lớn, nhưng bản dựng tiếp theo chỉ cần tập hợp con nhỏ của nó: 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ư tính năng vô hiệu hoá từ trên xuống chỉ dẫn đến đồ thị của bản dựng thứ hai.
Bazel chỉ vô hiệu hoá từ dưới lên.
Để tăng mức độ gia tăng, Bazel sử dụng tính năng giảm bớt thay đổi: nếu một nút là không hợp lệ, nhưng khi tạo lại, hệ thống phát hiện ra rằng giá trị mới của nó vẫn giữ nguyên là giá trị cũ, các nút đã mất hiệu lực do thay đổi trong nút này đều được "sống lại".
Điều này rất hữu ích, ví dụ: nếu một thay đổi một nhận xét trong tệp C++: thì
Tệp .o
được tạo từ đây 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 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 sẽ luôn được tạo lại từ đầu, ngay cả khi có thuật toán tốt hơn và thuật toá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ụ khi điều này hữu ích:
- Liên kết gia tăng
- Khi một tệp lớp đơn thay đổi trong tệp JAR, có thể sửa đổi tệp JAR tại chỗ thay vì tạo lại tệp từ đầu.
Lý do Bazel không hỗ trợ những việc này theo nguyên tắc là gấp đôi:
- Hiệu suất tăng lên rất hạn chế.
- Khó xác thực kết quả của trường hợp đột biến cũng giống như của việc xây dựng lại một cách gọn gàng và Google coi trọng các bản dựng từng chút một có thể lặp lại.
Cho đến bây giờ, có thể đạt được hiệu suất đủ tốt bằng cách phân tách một tạo bản dựng tốn kém và đánh giá lại một phần theo cách đó. Ví dụ: trong ứng dụng Android, bạn có thể chia tất cả các lớp thành nhiều nhóm và tệp dex chúng một cách riêng biệt. Theo đó, nếu các lớp trong một nhóm không thay đổi, thì hoạt động tạo tệp dex sẽ không cần phải làm lại.
Liên kết với các khái niệm Bazel
Đây là thông tin tóm tắt cấp cao về khoá SkyFunction
và SkyValue
Các phương thức triển khai 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 tồn 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 đồ thị Skyframe và không có phần phụ thuộc. - FileValue. Được sử dụng bởi bất cứ ai 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
và bất kỳ đường liên kết tượng trưng nào cần được giải quyết (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 làa/b
). Chiến lược phát hành đĩa đơn sự khác biệt giữaFileValue
vàFileStateValue
là quan trọng vì cái sau có thể được sử dụng trong trường hợp nội dung của tệp không thực sự cần thiết. Ví dụ: nội dung tệp không liên quan khi đánh giá các lỗi hệ thống tệp (chẳng hạn nhưsrcs=glob(["*/*.java"])
). - DirectoryListingStateValue. Kết quả của
readdir()
. ThíchFileStateValue
, đâ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 cứ ai quan tâm đến các mục nhập của
một thư mục. Phụ thuộc vào
DirectoryListingStateValue
tương ứng, như cũng nhưFileValue
được liên kết của 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 bắc cầu trên bất kỳDirectoryListingValue
dùng để phân giải các khối cầu nhỏ 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ộ dữ liệu
của tập hợp hành động được tạo ra 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 được định cấu hình phụ thuộc. Phụ thuộc vào
PackageValue
có mục tiêu tương ứng,ConfiguredTargetValues
phần phụ thuộc trực tiếp và một nút đặc biệt đại diện cho 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 tạo bản dựng. Tệp nguồn
phụ thuộc vào
FileValue
của nút 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ỳ 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 có chứa trong SkyKey của mình, điều này trái với quan niệm nên dùng SkyKeys nhỏ. Lưu ýActionExecutionValue
vàArtifactValue
sẽ không được dùng nếu giai đoạn thực thi không chạy.
Như một trợ giúp trực quan, sơ đồ này cho thấy mối quan hệ giữa Triển khai SkyFunction sau một bản dựng của chính Bazel: