Khung tranh tường

Báo cáo vấn đề Xem nguồn Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Mô hình đánh giá song song và mô hình tăng dần 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 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. Một tên ngắn không thể thay đổi để 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ần 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 có thể tạo một bản dựng 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 đến các nút lá. 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, Bazel sẽ kết thúc bằng 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à biểu đồ không tuần hoàn 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ượ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 hóa 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. Nhưng chính đường dẫn đó cũng có thể là một đường 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 giao diện đó. Dưới đâ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 có nút, giá trị của nút đó sẽ được trả về, nếu không, null sẽ được trả về và hàm 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, cùng một 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, cách 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 phải cẩn thận để hai hàm khác nhau không bị chồng chéo lên nhau. Nhìn chung, các hiệu ứng phụ khi ghi (trong đó dữ liệu chảy ra từ Bazel) là ổn, còn các hiệu ứng phụ khi đọc (trong đó dữ liệu chảy vào Bazel mà không có phần phụ thuộc đã đăng ký) thì không ổn, vì đó 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.

Việc triển khai SkyFunction đúng cách sẽ tránh 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ư bằng cách trực tiếp đọc hệ thống tệp), vì điều đó sẽ khiến 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 gia tăng 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 là 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, thì Bazel có thể đảm bảo rằng nếu trạng thái đầu vào giống nhau, thì dữ liệu trả về cũng giống nhau. Nếu tất cả các hàm sky đều có tính chất 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 chất xác định.
  • Tính tăng dần 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ả 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ả sẽ giống như khi các hàm này 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ừ tệp đầu vào đến 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 tạo lại: tập hợp các tệp đầu vào đã thay đổi.

Cụ thể, có hai chiến lược tăng dần có thể áp dụng: chiến lược từ dưới lên và chiến lược từ trên xuống. Cách nào 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 một biểu đồ được tạo và tập hợp các đầu vào đã thay đổi được biết, tất cả các nút sẽ bị vô hiệu hoá và phụ thuộc một cách bắc cầu vào các tệp đã thay đổi. Đây là cách 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 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 các tệp đó có bị thay đổi hay không. Bạn có thể cải thiện vấn đề 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, tập hợp đó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ó tập hợp đóng bắc cầu sạch mới được giữ lại. Điều này sẽ tốt 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 đồ đó: việc 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ư 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.

Bazel chỉ vô hiệu hoá từ dưới lên.

Để tăng tính gia tăng, Bazel sử dụng tính năng loại bỏ thay đổi: nếu một nút bị vô hiệu hoá, nhưng khi tạo lại, bạn 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 "hồi sinh".

Điều này rất hữu ích, ví dụ: 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 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à một vấn đề toàn bộ 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ẽ thay đổi giá trị cũ của nút dựa trên các thay đổi. Sau đây là một số ví dụ về trường hợp bạn 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 thay đổi trong tệp JAR, bạn có thể chỉnh sửa tệp JAR tại chỗ thay vì tạo lại tệp JAR từ đầu.

Có hai lý do khiến Bazel không hỗ trợ những điều này theo nguyên tắc:

  • Hiệu suất tăng lên không đáng kể.
  • 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 đánh giá cao 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à thực hiện việc đá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 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, bạn không cần phải tạo lại tệp dex.

Liên kết với các khái niệm của Bazel

Đây là thông tin tóm tắt cấp cao về các phương thức triển khai SkyFunctionSkyValue chính mà Bazel sử dụng để tạo bản dựng:

  • FileStateValue. Kết quả của lstat(). Đối với các tệp hiện có, hàm này 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 nào.
  • FileValue. Được sử 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 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 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). Việc phân biệt giữa FileValueFileStateValue là quan trọng vì bạn có thể sử dụng FileStateValue 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á glob 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 sử dụng bởi mọi thứ quan tâm đến các mục của 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ệp BUILD được liên kết, đồng thời cũng tuỳ thuộc vào mọi DirectoryListingValue được dùng để phân giải glob trong gói (cấu trúc dữ liệu đại diện cho nội dung của tệp BUILD trong nội bộ).
  • ConfiguredTargetValue. Biểu thị một mục tiêu đã định cấu hình, là một bộ dữ liệu gồm tập hợp các hành động đượ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, cho dù đó là một nguồn hay 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 tệp trong quá trình thực thi các bước xây dựng. Tệp nguồn phụ thuộc vào FileValue của nút liên kết và cấu phần phần mềm đầu ra 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. Biểu thị 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. Thao tác mà nó thực thi nằm trong SkyKey, điều này trái ngược với khái niệm SkyKey phải nhỏ. Xin lưu ý rằng ActionExecutionValueArtifactValue không được sử dụng nếu giai đoạn thực thi không chạy.

Dưới đây là sơ đồ minh hoạ mối quan hệ giữa các phương thức triển khai SkyFunction sau khi tạo bản dựng Bazel:

Biểu đồ về mối quan hệ triển khai SkyFunction