Khi viết quy tắc, lỗi hiệu suất phổ biến nhất là duyệt hoặc sao chép dữ liệu được tích luỹ từ các phần phụ thuộc. Khi được tổng hợp trên toàn bộ bản dựng, các thao tác này có thể dễ dàng chiếm thời gian hoặc không gian O(N^2). Để tránh điều này, bạn cần phải hiểu cách sử dụng depset một cách hiệu quả.
Việc này có thể khó thực hiện đúng cách, vì vậy, Bazel cũng cung cấp một trình phân tích bộ nhớ giúp bạn tìm ra những vị trí có thể đã mắc lỗi. Xin lưu ý: Chi phí viết một quy tắc không hiệu quả có thể không rõ ràng cho đến khi quy tắc đó được sử dụng rộng rãi.
Sử dụng depset
Bất cứ khi nào bạn tổng hợp thông tin từ các phần phụ thuộc của quy tắc, bạn nên sử dụng depset. Chỉ sử dụng danh sách hoặc từ điển thuần tuý để xuất bản thông tin cục bộ cho quy tắc hiện tại.
Một depset biểu thị thông tin dưới dạng biểu đồ lồng nhau cho phép chia sẻ.
Hãy xem xét biểu đồ sau:
C -> B -> A
D ---^
Mỗi nút xuất bản một chuỗi. Với depset, dữ liệu sẽ có dạng như sau:
a = depset(direct=['a'])
b = depset(direct=['b'], transitive=[a])
c = depset(direct=['c'], transitive=[b])
d = depset(direct=['d'], transitive=[b])
Xin lưu ý rằng mỗi mục chỉ được đề cập một lần. Với danh sách, bạn sẽ nhận được như sau:
a = ['a']
b = ['b', 'a']
c = ['c', 'b', 'a']
d = ['d', 'b', 'a']
Xin lưu ý rằng trong trường hợp này, 'a' được đề cập 4 lần! Với các biểu đồ lớn hơn, vấn đề này sẽ chỉ trở nên tồi tệ hơn.
Dưới đây là ví dụ về cách triển khai quy tắc sử dụng depset đúng cách để xuất bản thông tin bắc cầu. Xin lưu ý rằng bạn có thể xuất bản thông tin cục bộ của quy tắc bằng danh sách nếu muốn, vì đây không phải là O(N^2).
MyProvider = provider()
def _impl(ctx):
my_things = ctx.attr.things
all_things = depset(
direct=my_things,
transitive=[dep[MyProvider].all_things for dep in ctx.attr.deps]
)
...
return [MyProvider(
my_things=my_things, # OK, a flat list of rule-local things only
all_things=all_things, # OK, a depset containing dependencies
)]
Hãy xem trang tổng quan về depset để biết thêm thông tin.
Tránh gọi depset.to_list()
Bạn có thể chuyển đổi một depset thành danh sách phẳng bằng cách sử dụng
to_list(), nhưng việc này thường dẫn đến chi phí O(N^2)
chi phí. Nếu có thể, hãy tránh làm phẳng bất kỳ depset nào, ngoại trừ cho mục đích gỡ lỗi.
Một quan niệm sai lầm phổ biến là bạn có thể tự do làm phẳng depset nếu chỉ thực hiện việc này
ở các mục tiêu cấp cao nhất, chẳng hạn như quy tắc <xx>_binary, vì khi đó, chi phí sẽ không
tích luỹ trên mỗi cấp của biểu đồ bản dựng. Tuy nhiên, điều này vẫn là O(N^2) khi bạn tạo một tập hợp các mục tiêu có các phần phụ thuộc trùng lặp. Điều này xảy ra khi bạn tạo các bài kiểm thử //foo/tests/... hoặc khi nhập một dự án IDE.
Giảm số lần gọi đến depset
Việc gọi depset bên trong một vòng lặp thường là một lỗi. Điều này có thể dẫn đến các depset có độ lồng ghép rất sâu, hoạt động kém. Ví dụ:
x = depset()
for i in inputs:
# Do not do that.
x = depset(transitive = [x, i.deps])
Bạn có thể dễ dàng thay thế mã này. Trước tiên, hãy thu thập các depset bắc cầu và hợp nhất tất cả cùng một lúc:
transitive = []
for i in inputs:
transitive.append(i.deps)
x = depset(transitive = transitive)
Đôi khi, bạn có thể giảm số lần gọi này bằng cách sử dụng danh sách hiểu:
x = depset(transitive = [i.deps for i in inputs])
Sử dụng ctx.actions.args() cho dòng lệnh
Khi tạo dòng lệnh, bạn nên sử dụng ctx.actions.args(). Điều này sẽ trì hoãn việc mở rộng bất kỳ depset nào sang giai đoạn thực thi.
Ngoài việc nhanh hơn, điều này sẽ giúp giảm mức tiêu thụ bộ nhớ của các quy tắc – đôi khi giảm 90% trở lên.
Dưới đây là một số mẹo:
Truyền trực tiếp depset và danh sách dưới dạng đối số, thay vì tự làm phẳng chúng. Chúng sẽ được
ctx.actions.args()mở rộng cho bạn. Nếu bạn cần bất kỳ phép biến đổi nào trên nội dung depset, hãy xem ctx.actions.args#add để xem có nội dung nào phù hợp hay không.Bạn có đang truyền
File#pathdưới dạng đối số không? Không cần. Mọi Tệp đều tự động chuyển thành đường dẫn của tệp đó , bị trì hoãn đến thời gian mở rộng.Tránh tạo chuỗi bằng cách nối các chuỗi lại với nhau. Đối số chuỗi tốt nhất là một hằng số vì bộ nhớ của đối số này sẽ được chia sẻ giữa tất cả các thực thể của quy tắc.
Nếu các đối số quá dài đối với dòng lệnh, thì đối tượng
ctx.actions.args()có thể được ghi có điều kiện hoặc vô điều kiện vào tệp tham số bằng cách sử dụngctx.actions.args#use_param_file. Điều này được thực hiện ở chế độ nền khi hành động được thực thi. Nếu cần kiểm soát rõ ràng tệp tham số, bạn có thể ghi tệp đó theo cách thủ công bằng cách sử dụngctx.actions.write.
Ví dụ:
def _impl(ctx):
...
args = ctx.actions.args()
file = ctx.declare_file(...)
files = depset(...)
# Bad, constructs a full string "--foo=<file path>" for each rule instance
args.add("--foo=" + file.path)
# Good, shares "--foo" among all rule instances, and defers file.path to later
# It will however pass ["--foo", <file path>] to the action command line,
# instead of ["--foo=<file_path>"]
args.add("--foo", file)
# Use format if you prefer ["--foo=<file path>"] to ["--foo", <file path>]
args.add(file, format="--foo=%s")
# Bad, makes a giant string of a whole depset
args.add(" ".join(["-I%s" % file.short_path for file in files.to_list()])
# Good, only stores a reference to the depset
args.add_all(files, format_each="-I%s", map_each=_to_short_path)
# Function passed to map_each above
def _to_short_path(f):
return f.short_path
Đầu vào hành động bắc cầu phải là depset
Khi tạo một hành động bằng cách sử dụng ctx.actions.run, đừng
quên rằng trường inputs chấp nhận một depset. Hãy sử dụng trường này bất cứ khi nào đầu vào được thu thập từ các phần phụ thuộc một cách bắc cầu.
inputs = depset(...)
ctx.actions.run(
inputs = inputs, # Do *not* turn inputs into a list
...
)
Treo
Nếu Bazel có vẻ bị treo, bạn có thể nhấn Ctrl-\ hoặc gửi
Bazel tín hiệu SIGQUIT (kill -3 $(bazel info server_pid)) để lấy kết xuất luồng
trong tệp $(bazel info output_base)/server/jvm.out.
Vì bạn có thể không chạy được bazel info nếu bazel bị treo, nên thư mục
output_base thường là thư mục mẹ của liên kết tượng trưng bazel-<workspace>
trong thư mục không gian làm việc.
Phân tích hiệu suất
Hồ sơ theo dõi JSON có thể rất hữu ích để nhanh chóng hiểu được thời gian Bazel đã dành cho việc gì trong quá trình gọi.
Bạn có thể sử dụng cờ --experimental_command_profile
để ghi lại hồ sơ Java Flight Recorder thuộc nhiều loại
(thời gian CPU, thời gian thực, phân bổ bộ nhớ và tranh chấp khoá).
Bạn có thể sử dụng cờ --starlark_cpu_profile
để ghi hồ sơ pprof về mức sử dụng CPU của tất cả các luồng Starlark.
Phân tích bộ nhớ
Bazel đi kèm với một trình phân tích bộ nhớ tích hợp có thể giúp bạn kiểm tra mức sử dụng bộ nhớ của quy tắc. Nếu có vấn đề, bạn có thể kết xuất vùng nhớ khối xếp để tìm dòng mã chính xác gây ra vấn đề.
Bật tính năng theo dõi bộ nhớ
Bạn phải truyền 2 cờ khởi động này đến mọi lệnh gọi Bazel:
STARTUP_FLAGS=\
--host_jvm_args=-javaagent:<path to java-allocation-instrumenter-3.3.4.jar> \
--host_jvm_args=-DRULE_MEMORY_TRACKER=1
Các cờ này sẽ khởi động máy chủ ở chế độ theo dõi bộ nhớ. Nếu bạn quên các cờ này dù chỉ một lần gọi Bazel, máy chủ sẽ khởi động lại và bạn sẽ phải bắt đầu lại.
Sử dụng Trình theo dõi bộ nhớ
Ví dụ: hãy xem mục tiêu foo và xem mục tiêu này làm gì. Để chỉ chạy quá trình phân tích và không chạy giai đoạn thực thi bản dựng, hãy thêm cờ --nobuild.
$ bazel $(STARTUP_FLAGS) build --nobuild //foo:foo
Tiếp theo, hãy xem toàn bộ thực thể Bazel tiêu thụ bao nhiêu bộ nhớ:
$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc
> 2594MB
Phân tích theo lớp quy tắc bằng cách sử dụng bazel dump --rules:
$ bazel $(STARTUP_FLAGS) dump --rules
>
RULE COUNT ACTIONS BYTES EACH
genrule 33,762 33,801 291,538,824 8,635
config_setting 25,374 0 24,897,336 981
filegroup 25,369 25,369 97,496,272 3,843
cc_library 5,372 73,235 182,214,456 33,919
proto_library 4,140 110,409 186,776,864 45,115
android_library 2,621 36,921 218,504,848 83,366
java_library 2,371 12,459 38,841,000 16,381
_gen_source 719 2,157 9,195,312 12,789
_check_proto_library_deps 719 668 1,835,288 2,552
... (more output)
Xem vị trí bộ nhớ đang đi bằng cách tạo tệp pprof bằng cách sử dụng bazel dump --skylark_memory:
$ bazel $(STARTUP_FLAGS) dump --skylark_memory=$HOME/prof.gz
> Dumping Starlark heap to: /usr/local/google/home/$USER/prof.gz
Sử dụng công cụ pprof để điều tra vùng nhớ khối xếp. Điểm xuất phát tốt là lấy biểu đồ ngọn lửa bằng cách sử dụng pprof -flame $HOME/prof.gz.
Tải pprof xuống từ https://github.com/google/pprof.
Lấy kết xuất văn bản của các vị trí gọi nóng nhất được chú thích bằng các dòng:
$ pprof -text -lines $HOME/prof.gz
>
flat flat% sum% cum cum%
146.11MB 19.64% 19.64% 146.11MB 19.64% android_library <native>:-1
113.02MB 15.19% 34.83% 113.02MB 15.19% genrule <native>:-1
74.11MB 9.96% 44.80% 74.11MB 9.96% glob <native>:-1
55.98MB 7.53% 52.32% 55.98MB 7.53% filegroup <native>:-1
53.44MB 7.18% 59.51% 53.44MB 7.18% sh_test <native>:-1
26.55MB 3.57% 63.07% 26.55MB 3.57% _generate_foo_files /foo/tc/tc.bzl:491
26.01MB 3.50% 66.57% 26.01MB 3.50% _build_foo_impl /foo/build_test.bzl:78
22.01MB 2.96% 69.53% 22.01MB 2.96% _build_foo_impl /foo/build_test.bzl:73
... (more output)