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à các 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 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 bất biến để tham chiếu đến SkyValue, ví dụ: FILECONTENTS:/tmp/foo hoặc PACKAGE://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 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 bao gồm việc đánh giá nút đại diện cho yêu cầu xây dựng (đây là trạng thái mà chúng tôi đang nỗ lực đạt được, nhưng có rất nhiều mã cũ cản trở). Trước tiên, SkyFunction của bản dựng sẽ được tìm thấy và gọi bằng khoá của SkyKey cấp cao nhất. Sau đó, hàm sẽ 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 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 sẽ nhận đượ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à biểu đồ chu kỳ có hướng của các phần phụ thuộc giữa các nút 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 cần thiết để 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 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 nú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 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 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, 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 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ọi env.getValue tươ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(). 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 khi gọi
  • Có tác dụng phụ, ví dụ: ghi tệp vào hệ thống tệp. Bạn cần cẩn thận để hai hàm khác nhau không ảnh hưởng đế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à được phép, các 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ý) là không được phép, 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 cách triển khai SkyFunction không được 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ư đọ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 của mình, 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, 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 trên Sky đều mang tính xác định, thì điều này có nghĩa là toàn bộ bản dựng 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 tất cả các hàm được ghi lại, Bazel chỉ có thể vô hiệu hoá chính xác 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 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 biểu đồ luồng dữ liệu hoàn chỉnh từ tệp đầu vào đến tệp đầu ra và chỉ sử dụng thông tin này để 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 phụ thuộc vào hình dạng của biểu đồ phụ thuộc.

  • Trong quá trình vô hiệu hoá 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 phụ thuộc bắc cầu vào các tệp đã thay đổi sẽ bị vô hiệu hoá. Chiến lược này là tối ưu nếu chúng ta biết rằng nút cấp cao nhất tương tự sẽ được xây dựng 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 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ụng inotify hoặc 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 sạch sẽ được giữ lại. Chiến lược này sẽ tốt hơn nếu chúng ta biết rằng biểu đồ nút hiện tại có kích thước 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: 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.

Hiện tại, chúng tôi chỉ thực hiện quá trình vô hiệu hoá từ dưới lên.

Để tăng thêm mức độ gia tăng, chúng tôi 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 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ị vô hiệu hoá do thay đổi trong nút này sẽ được "khôi phục".

Ví dụ: tính năng này hữu ích nếu bạn 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 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á một nút là 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 tồn tại 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. Dưới đây là một vài ví dụ về trường hợp tính năng này sẽ hữu ích:

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

Lý do Bazel hiện không hỗ trợ những điều này theo nguyên tắc (chúng tôi có một số mức độ hỗ trợ cho việc liên kết gia tăng, nhưng tính năng này không được triển khai trong Skyframe) là do 2 lý do: chúng tôi chỉ đạt được mức tăng hiệu suất hạn chế và khó đảm bảo 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ẽ, đồng thời Google coi trọng các bản dựng có thể lặp lại từng bit.

Cho đến nay, chúng tôi 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 quá trình đánh giá lại một phần theo cách đó: quá trình này sẽ chia tất cả các lớp trong một ứng dụng thành nhiều nhóm và thực hiện dexing riêng trên các nhóm đó. Bằng cách này, nếu các lớp trong một nhóm không thay đổi, thì quá trình dexing không cần phải thực hiện lại.

Ánh xạ đến các khái niệm của 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 để thực hiện 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êm thông tin để 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 mọi thứ quan tâm đến nội dung thực tế và/hoặc đường dẫn đã phân giải của tệp. Phụ thuộc vào FileStateValue tương ứng và mọi liên kết tượng trưng 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 glob 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 là kết quả của readdir(). Phụ thuộc vào FileValue được liên kết với 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 FileValue của tệp BUILD được liên kết, đồng thời phụ thuộc bắc cầu vào mọi DirectoryListingValue đượ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ệp BUILD bên trong)
  • 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 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. Phụ 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. Đại diện cho một tệp trong bản dựng, có thể là nguồn hoặc thành phần đầu ra (thành phần 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). Đối với các tệp nguồn, thành phần này phụ thuộc vào FileValue của nút được liên kết, đối với các thành phần đầu ra, thành phần này phụ thuộc vào ActionExecutionValue của bất kỳ hành động nào tạo ra thành phần đó.
  • ActionExecutionValue. Đại diện cho việc 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à thành phần này thực thi hiện nằm trong khoá Sky của thành phần đó, điều này trái ngược với khái niệm rằng khoá Sky phải nhỏ. Chúng tôi đang nỗ lực giải quyết sự khác biệt này (xin lưu ý rằng ActionExecutionValueArtifactValue không được dùng nếu chúng tôi không chạy giai đoạn thực thi trên Skyframe).