Khi viết quy tắc, sai sót phổ biến nhất về hiệu suất là truyền tải 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ộ tạo, các thao tác này có thể dễ dàng mất O(N^2) thời gian hoặc không gian. Để tránh điều này, là rất quan trọng để hiểu cách sử dụng phần phụ thuộc hiệu quả.
Việc này có thể khó thực hiện, vì vậy Bazel cũng cung cấp một trình phân tích bộ nhớ hỗ trợ bạn tìm ra những vị trí mà bạn có thể đã mắc lỗi. Lưu ý: Chi phí viết một quy tắc không hiệu quả có thể chưa rõ ràng cho đến khi quy tắc đó được được sử dụng rộng rãi.
Sử dụng phần phụ thuộc
Bất cứ khi nào bạn cuộn lên thông tin từ các phần phụ thuộc quy tắc, bạn nên sử dụng depsets. Chỉ sử dụng danh sách hoặc tập hợp từ điển thuần tuý để phát hành thông tin cục bộ cho quy tắc hiện tại.
Phần phụ thuộc 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 phát hành một chuỗi duy nhất. 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ẽ có:
a = ['a']
b = ['b', 'a']
c = ['c', 'b', 'a']
d = ['d', 'b', 'a']
Lưu ý rằng trong trường hợp này, 'a'
được đề cập bốn lần! Với biểu đồ lớn hơn,
thì vấn đề 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 phần tách một cách chính xác để xuất bản thông tin bắc cầu. Xin lưu ý rằng bạn có thể phát hành thông tin cục bộ theo quy tắc bằng cách sử dụ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
)]
Xem trang tổng quan về bộ lập trình để biết thêm thông tin.
Tránh gọi depset.to_list()
Bạn có thể ép buộc 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). Nếu có thể, hãy tránh mọi trường hợp làm phẳng bộ đếm ngoại trừ việc 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 các nhóm phần phụ thuộc nếu chỉ làm điều đó ở các mục tiêu cấp cao nhất, chẳng hạn như quy tắc <xx>_binary
, vì sau đó chi phí sẽ không được tích luỹ trên từng cấp của biểu đồ bản dựng. Nhưng đây vẫn 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 chồng chéo nhau. Điều này xảy ra khi bạn tạo //foo/tests/...
kiểm thử hoặc khi nhập dự án IDE.
Giảm số lượng cuộc gọi xuống depset
Việc gọi depset
trong vòng lặp thường là một lỗi. Điều này có thể dẫn đến phần phụ thuộc với
lồng 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ể thay thế mã này một cách dễ dàng. Trước tiên, hãy thu thập các phần phụ thuộc 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 thiểu việc này bằng cách sử dụng cú pháp liệt kê:
x = depset(transitive = [i.deps for i in inputs])
Sử dụng ctx.actions.args() cho các dòng lệnh
Khi tạo các dòng lệnh, bạn nên sử dụng ctx.actions.args(). Điều này trì hoãn việc mở rộng mọi phần phụ thuộc vào giai đoạn thực thi.
Ngoài việc nhanh hơn, điều này còn giảm mức tiêu thụ bộ nhớ của các quy tắc của mình -- đôi khi là 90% trở lên.
Dưới đây là một số thủ thuật:
Truyền các phần giải mã và danh sách trực tiếp dưới dạng đối số, thay vì làm phẳng chúng chính bạn. Các biến này sẽ được mở rộng theo
ctx.actions.args()
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ó gì phù hợp không.Bạn có đang truyền
File#path
làm đối số không? Không cần. Mọi Tệp đều tự động được chuyển thành đường dẫn, được 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 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 args 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 không có điều kiện vào tệp tham số bằng cách sử dụngctx.actions.args#use_param_file
. Việc 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 params, bạn có thể ghi tệp đó theo cách thủ công bằ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(format="--foo=%s", value=file)
# Bad, makes a giant string of a whole depset
args.add(" ".join(["-I%s" % file.short_path for file in files])
# 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
Dữ liệu đầu vào của hành động bắc cầu phải là depset
Khi tạo một hành động bằng ctx.actions.run, đừng quên rằng trường inputs
chấp nhận một depset. Sử dụng phương thức này bất cứ khi nào dữ liệu đầ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 tổ hợp phím Ctrl-\ hoặc gửi cho Bazel một tín hiệu SIGQUIT
(kill -3 $(bazel info server_pid)
) để nhận tệp báo lỗi 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 đường 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 Bazel đã dành thời gian cho việc gì trong quá trình gọi.
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 sẵn 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 đang gây ra vấn đề.
Bật tính năng theo dõi bộ nhớ
Bạn phải truyền hai 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.0.jar> \
--host_jvm_args=-DRULE_MEMORY_TRACKER=1
Các lệnh này sẽ khởi động máy chủ ở chế độ theo dõi bộ nhớ. Nếu bạn quên các cài đặt này vì một lệnh 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 foo
mục tiêu và xem mục tiêu này làm gì. Để chỉ chạy quy 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 mức tiêu thụ bộ nhớ của toàn bộ thực thể Bazel:
$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc
> 2594MB
Hãy chia nhỏ quy tắc 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)
Hãy xem nơi bộ nhớ sẽ được chuyển đến bằng cách tạo một tệp pprof
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. Bạn có thể bắt đầu bằng cách tạo biểu đồ hình ngọn lửa bằng pprof -flame $HOME/prof.gz
.
Tải pprof
qua https://github.com/google/pprof.
Nhận tệp kết xuất văn bản của các vị trí gọi phổ biến 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)