Khung tranh tường

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 tạo trong suốt 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à mục tiêu đã định cấu hình.
  • SkyKey. Một tên ngắn không thể thay đổi để tham chiếu đến một SkyValue, ví dụ: FILECONTENTS:/tmp/foo hoặc PACKAGE://foo.
  • SkyFunction. Tạo các nút dựa trên các khoá và nút phụ thuộc của chúng.
  • Biểu đồ nú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 Bazel là dựa trên cơ sở đó.

Đánh giá

Một bản dựng bao gồm việc đá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 cố gắng đạt được, nhưng có rất nhiều mã cũ trên hướng này). Trước tiên, hệ thống sẽ tìm thấy SkyFunction và gọi bằng 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 hàm khác, v.v. cho đến khi đạt đến 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 cuối cùng là giá trị của SkyValue cấp cao nhất, một số hiệu ứng phụ (chẳng hạn như tệp đầu ra trong hệ thống tệp) và một đồ thị tuần hoàn có định hướng về 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á một nút tệp đầu vào hoá ra 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 đó 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 mục tiêu của đường liên kết tượng trưng. Tuy nhiên, bản thân đó cũng có thể là một đường liên kết tượng trưng, trong trường hợp này, hàm ban đầu cũng cần phải tìm nạp mục tiêu.

Các hàm được biểu thị trong mã bằng giao diện SkyFunction và dịch vụ được cung cấp cho hàm đó bằng một 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 này có sẵn, giá trị của nút sẽ được trả về, nếu không thì null sẽ đượ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 được đánh giá, sau đó trình tạo nút ban đầu được gọi lại, nhưng lần này chính lệnh gọi env.getValue sẽ trả về một giá trị không phải 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, điều này 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 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. Hãy cẩn thận để hai chức năng khác nhau không dẫm lên ngón chân nhau. Nhìn chung, việc ghi các hiệu ứng phụ (trong đó dữ liệu tràn ra từ Bazel) đều ổn, hãy đọc các hiệu ứng phụ (trong đó dữ liệu truyền vào Bazel mà không cần phần phụ thuộc đã đăng ký) thì không, vì đây 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 đọc, 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ề một giá trị không phải null cho biết là đã hoàn thành.

Chiến lược đánh giá này có một số lợi ích:

  • Độ mạnh. Nếu các hàm chỉ yêu cầu dữ liệu đầu vào thông qua việc 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 tương tự sẽ được trả về. Nếu tất cả các hàm bầu trời là tất định, điều này có nghĩa là toàn bộ công trình cũng sẽ mang 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 mọi hàm đều đượ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 vô hiệu hoá 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 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, 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ỉ xây dựng lại những nút thực sự cần được xây dựng 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ó 2 chiến lược gia tăng có thể có: một chiến lược từ dưới lên và một chiến lược từ trên xuống. Đường dẫn nào là tối ưu phụ 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 tạo biểu đồ và biết được tập hợp các đầu vào được thay đổi, tất cả các nút bị vô hiệu hoá do phụ thuộc bắc cầu vào các tệp đã thay đổi. Đây là cách tối ưu nếu chúng ta biết rằng nút cấp cao nhất đó sẽ được tạo lại. Lưu ý rằng tính năng 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 các tệp đó đã thay đổi hay chưa. Bạn có thể cải thiện điều này bằng cách sử dụng inotify 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, nút đóng bắc cầu của nút cấp cao nhất được kiểm tra và chỉ các nút đó mới được giữ lại mà phần đóng bắc cầu mới được giữ lại. Điều này 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 đồ đó trong bản dựng tiếp theo: vô hiệu hóa từ dưới lên sẽ vô hiệu hóa biểu đồ lớn hơn của bản dựng đầu tiên, không giống như vô hiệu hóa từ trên xuống, chỉ bỏ 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.

Để tăng mức độ gia tăng, chúng tôi sử dụng tính năng cắt bớt thay đổi: nếu một nút không hợp lệ, nhưng khi xây dựng lại, hệ thống sẽ phát hiện ra rằng giá trị mới của nó giống với giá trị cũ, các nút đã bị vô hiệu hoá do thay đổi trong nút này sẽ được "khôi phục".

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ẽ không thay đổi, do đó, chúng ta 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 vô hiệu hoá nút là vấn đề tất cả hoặc không có gì: khi 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 để làm thay đổi giá trị cũ của nút dựa trên các thay đổi. Một số ví dụ có thể hữu ích:

  • Liên kết gia tăng
  • Khi một tệp .class thay đổi trong .jar, về mặt lý thuyết, chúng ta có thể sửa đổi tệp .jar thay vì tạo lại tệp từ đầu.

Lý do Bazel hiện không hỗ trợ những tính năng 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) là hai lý do: 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 sự thay đổi này sẽ giống như kết quả 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 theo từng bit.

Cho đến nay, chúng ta luôn có thể đạt được hiệu suất đủ tốt chỉ bằng cách phân tách một bước xây dựng tốn kém và đánh giá lại một phần theo cách đó: nó chia tất cả các lớp trong ứng dụng thành nhiều nhóm và thực hiện tạo tệp dex riêng trên các lớp đó. 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 việc tạo tệp dex.

Ánh xạ tới các khái niệm của Bazel

Đây là tổng quan sơ bộ về một số phương thức triển khai SkyFunction mà Bazel sử dụng để tạo bản dựng:

  • FileStateValue. Kết quả của một 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 (Giá trị tệp). Dùng trong bất kỳ chương trình nào quan tâm đến nội dung thực và/hoặc đường dẫn đã được phân giải của tệp. Tuỳ thuộc vào FileStateValue tương ứng và bất kỳ đường liên kết tượng trưng nào cần được phân giải (chẳng hạn như FileValue cho a/b cần đường dẫn đã phân giải của a và đường dẫn đã phân giải của a/b). Sự khác biệt giữa FileStateValue là rất quan trọng vì trong một số trường hợp (ví dụ: đánh giá các globs của hệ thống tệp (chẳng hạn như srcs=glob(["*/*.java"])) thì nội dung của tệp là không thực sự cần thiết.
  • DirectoryListingValue. Về cơ bản là kết quả của readdir(). Tuỳ thuộc vào FileValue liên kết với thư mục đó.
  • PackageValue (Giá trị gói). Đại diện cho phiên bản được phân tích cú pháp của tệp BUILD. Phụ thuộc vào FileValue của tệp BUILD được liên kết, cũng như bắc cầu vào bất kỳ DirectoryListingValue nào được dùng để phân giải các globs trong gói (cấu trúc dữ liệu biểu thị nội dung của tệp BUILD trong nội bộ)
  • ConfiguredTargetValue. Đại diện cho một mục tiêu được định cấu hình. Đây là một tập hợp gồm các thao tác đượ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 được định cấu hình phụ thuộc vào mục tiêu này. Phụ thuộc vào PackageValue có đích tương ứng, 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 (Giá trị cấu phần phần mềm). 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ấ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 các bước bản dựng trên thực tế). Đối với tệp nguồn, chế độ này phụ thuộc vào FileValue của nút được liên kết. Đối với cấu phần phần mềm đầu ra, chế độ này phụ thuộc vào ActionExecutionValue của bất kỳ hành động 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. Phụ thuộc vào ArtifactValues của tệp đầu vào. Thao tác được thực thi hiện nằm trong khoá bầu trời. Điều này trái ngược với quan niệm cho rằng khoá bầu trời phải có kích thước nhỏ. Chúng tôi đang nỗ lực giải quyết sự khác biệt này (lưu ý rằng ActionExecutionValueArtifactValue sẽ không được dùng nếu chúng ta không chạy giai đoạn thực thi trên Skyframe).