هنگام نوشتن قوانین، رایج ترین دام عملکرد عبور یا کپی کردن داده هایی است که از وابستگی ها انباشته شده اند. هنگامی که در کل ساخت جمع شوند، این عملیات به راحتی می تواند زمان یا فضای O(N^2) را بگیرد. برای جلوگیری از این امر، درک نحوه استفاده موثر از depset بسیار مهم است.
درست کردن این کار می تواند سخت باشد، بنابراین Bazel همچنین یک نمایه کننده حافظه ارائه می دهد که به شما در یافتن نقاطی که ممکن است اشتباه کرده باشید کمک می کند. هشدار داده شود: هزینه نوشتن یک قانون ناکارآمد ممکن است تا زمانی که به طور گسترده مورد استفاده قرار نگیرد، آشکار نباشد.
از depsets استفاده کنید
هر زمان که اطلاعات مربوط به قوانین وابستگی را جمع آوری می کنید، باید از depsets استفاده کنید. فقط از فهرست ها یا دستورات ساده برای انتشار اطلاعات محلی به قانون فعلی استفاده کنید.
یک depset اطلاعات را به عنوان یک نمودار تودرتو نشان می دهد که امکان اشتراک گذاری را فراهم می کند.
نمودار زیر را در نظر بگیرید:
C -> B -> A
D ---^
هر گره یک رشته را منتشر می کند. با دیپست ها داده ها به این شکل به نظر می رسند:
a = depset(direct=['a'])
b = depset(direct=['b'], transitive=[a])
c = depset(direct=['c'], transitive=[b])
d = depset(direct=['d'], transitive=[b])
توجه داشته باشید که هر مورد فقط یک بار ذکر شده است. با لیست ها این را دریافت خواهید کرد:
a = ['a']
b = ['b', 'a']
c = ['c', 'b', 'a']
d = ['d', 'b', 'a']
توجه داشته باشید که در این مورد 'a'
چهار بار ذکر شده است! با نمودارهای بزرگتر این مشکل بدتر خواهد شد.
در اینجا نمونه ای از اجرای قانون است که از depsets به درستی برای انتشار اطلاعات انتقالی استفاده می کند. توجه داشته باشید که در صورت تمایل، انتشار اطلاعات قوانین محلی با استفاده از لیستها اشکالی ندارد زیرا 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
)]
برای اطلاعات بیشتر به صفحه نمای کلی depset مراجعه کنید.
اجتناب از فراخوانی depset.to_list()
شما میتوانید با استفاده از to_list()
یک depset را به یک لیست مسطح وادار کنید، اما انجام این کار معمولاً منجر به هزینه O(N^2) میشود. در صورت امکان، از هر گونه مسطح کردن دیپست ها به جز برای اهداف اشکال زدایی خودداری کنید.
یک تصور غلط رایج این است که اگر این کار را فقط در اهداف سطح بالا انجام دهید، مانند یک قانون <xx>_binary
، می توانید آزادانه دپست ها را صاف کنید، زیرا از آن زمان هزینه در هر سطح از نمودار ساخت انباشته نمی شود. اما وقتی مجموعهای از اهداف با وابستگیهای همپوشانی ایجاد میکنید، همچنان O(N^2) است. این اتفاق در هنگام ساختن تستهای شما //foo/tests/...
یا هنگام وارد کردن یک پروژه IDE رخ میدهد.
تعداد تماس ها را با depset
دهید
فراخوانی depset
در داخل یک حلقه اغلب یک اشتباه است. می تواند منجر به فرورفتگی با لانه سازی بسیار عمیق شود که عملکرد ضعیفی دارد. مثلا:
x = depset()
for i in inputs:
# Do not do that.
x = depset(transitive = [x, i.deps])
این کد را می توان به راحتی جایگزین کرد. ابتدا، depset های انتقالی را جمع آوری کنید و همه آنها را به یکباره ادغام کنید:
transitive = []
for i in inputs:
transitive.append(i.deps)
x = depset(transitive = transitive)
گاهی اوقات میتوان با استفاده از درک فهرست، این را کاهش داد:
x = depset(transitive = [i.deps for i in inputs])
برای خطوط فرمان از ctx.actions.args() استفاده کنید
هنگام ساخت خطوط فرمان باید از ctx.actions.args() استفاده کنید. این گسترش هر گونه depset را به مرحله اجرا موکول می کند.
این کار جدا از اینکه به شدت سریعتر است، مصرف حافظه قوانین شما را کاهش میدهد - گاهی اوقات تا 90٪ یا بیشتر.
در اینجا چند ترفند وجود دارد:
به جای اینکه خودتان آنها را صاف کنید، دپست ها و لیست ها را مستقیماً به عنوان آرگومان ارسال کنید. آنها توسط
ctx.actions.args()
برای شما گسترش خواهند یافت. اگر به هر گونه تغییری در محتوای depset نیاز دارید، به ctx.actions.args#add نگاه کنید تا ببینید آیا چیزی مناسب است یا خیر.آیا
File#path
به عنوان آرگومان ارسال می کنید؟ نیازی نیست. هر فایلی بهطور خودکار به مسیر خود تبدیل میشود و به زمان گسترش موکول میشود.از ساختن رشته ها با به هم پیوستن آنها خودداری کنید. بهترین آرگومان رشته ای یک ثابت است زیرا حافظه آن بین تمام نمونه های قانون شما به اشتراک گذاشته می شود.
اگر args برای خط فرمان خیلی طولانی باشد، یک شی
ctx.actions.args()
می تواند به صورت مشروط یا بدون قید و شرط در یک فایل param با استفاده ازctx.actions.args#use_param_file
شود. این کار در پشت صحنه زمانی که اکشن اجرا می شود انجام می شود. اگر نیاز به کنترل صریح فایل params دارید، می توانید آن را به صورت دستی با استفاده ازctx.actions.write
.
مثال:
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
ورودیهای کنش گذرا باید depsets باشند
هنگام ساخت یک اکشن با استفاده از ctx.actions.run ، فراموش نکنید که فیلد inputs
یک depset را می پذیرد. هر زمان که ورودی ها از وابستگی ها به صورت گذرا جمع آوری می شوند از این استفاده کنید.
inputs = depset(...)
ctx.actions.run(
inputs = inputs, # Do *not* turn inputs into a list
...
)
حلق آویز کردن
اگر به نظر می رسد Bazel آویزان است، می توانید Ctrl-\ را فشار دهید یا یک سیگنال SIGQUIT
برای Bazel ارسال کنید ( kill -3 $(bazel info server_pid)
) تا یک موضوع در فایل $(bazel info output_base)/server/jvm.out
.
از آنجایی که اگر bazel آویزان باشد، ممکن است نتوانید bazel info
را اجرا کنید، دایرکتوری output_base
معمولاً والد bazel-<workspace>
در فهرست فضای کاری شما است.
پروفایل عملکرد
Bazel به طور پیش فرض یک نمایه JSON در command.profile.gz
در پایه خروجی می نویسد. می توانید مکان را با پرچم --profile
پیکربندی کنید، به عنوان مثال --profile=/tmp/profile.gz
. مکان هایی که به .gz
ختم می شوند با GZIP فشرده می شوند.
برای مشاهده نتایج، chrome://tracing
را در برگه مرورگر کروم باز کنید، روی «بارگیری» کلیک کنید و فایل نمایه (احتمالاً فشرده) را انتخاب کنید. برای نتایج دقیق تر، روی کادرهای گوشه پایین سمت چپ کلیک کنید.
برای پیمایش می توانید از این کنترل های صفحه کلید استفاده کنید:
-
1
را برای حالت "انتخاب" فشار دهید. در این حالت، می توانید کادرهای خاصی را برای بررسی جزئیات رویداد انتخاب کنید (به گوشه پایین سمت چپ مراجعه کنید). چندین رویداد را برای دریافت خلاصه و آمار انبوه انتخاب کنید. -
2
را برای حالت "pan" فشار دهید. سپس ماوس را بکشید تا نما حرکت کند. همچنین می توانید ازa
/d
برای حرکت به چپ/راست استفاده کنید. -
3
را برای حالت "zoom" فشار دهید. سپس ماوس را بکشید تا زوم کنید. همچنین می توانید ازw
/s
برای بزرگنمایی/کوچکنمایی استفاده کنید. -
4
را برای حالت "زمان بندی" فشار دهید، جایی که می توانید فاصله بین دو رویداد را اندازه گیری کنید. - فشار
?
برای یادگیری در مورد تمام کنترل ها
اطلاعات پروفایل
نمایه نمونه:
شکل 1. نمایه نمونه.
چند ردیف خاص وجود دارد:
-
action counters
: تعداد اقدامات همزمان در حال پرواز را نشان می دهد. برای دیدن مقدار واقعی روی آن کلیک کنید. باید تا ارزش--jobs
در ساخت های تمیز بالا رود. -
cpu counters
: برای هر ثانیه از ساخت، مقدار CPU استفاده شده توسط Bazel را نمایش میدهد (مقدار 1 برابر است با 100٪ مشغول بودن یک هسته). -
Critical Path
: یک بلوک را برای هر عمل در مسیر بحرانی نمایش می دهد. -
grpc-command-1
: موضوع اصلی Bazel. برای دریافت تصویری در سطح بالا از کارهایی که Bazel انجام می دهد مفید است، برای مثال "Launch Bazel"، "evaluateTargetPatterns"، و "runAnalysisPhase". -
Service Thread
: مکث های جزئی و عمده جمع آوری زباله (GC) را نمایش می دهد.
سطرهای دیگر نشان دهنده رشته های Bazel هستند و همه رویدادهای آن رشته را نشان می دهند.
مشکلات رایج عملکرد
هنگام تجزیه و تحلیل پروفایل های عملکرد، به دنبال موارد زیر باشید:
- مرحله تحلیل کندتر از حد انتظار (
runAnalysisPhase
)، به ویژه در ساخت های افزایشی. این می تواند نشانه ای از اجرای ضعیف قانون باشد، به عنوان مثال یکی که depsets را صاف می کند. بارگیری بسته می تواند با مقدار بیش از حد اهداف، ماکروهای پیچیده یا گلوب های بازگشتی کند شود. - اقدامات آهسته فردی، به ویژه آنهایی که در مسیر بحرانی هستند. ممکن است بتوان اقدامات بزرگ را به چندین اقدام کوچکتر تقسیم کرد یا مجموعه ای از وابستگی های (گذرا) را برای سرعت بخشیدن به آنها کاهش داد. همچنین بالا بودن غیر معمول غیر
PROCESS_TIME
(مانندREMOTE_SETUP
یاFETCH
) را بررسی کنید. - Bottlenecks، یعنی تعداد کمی از رشته ها مشغول هستند در حالی که بقیه در حال بیکاری هستند / منتظر نتیجه هستند (حدود 15-30 ثانیه را در تصویر بالا ببینید). بهینهسازی این امر به احتمال زیاد مستلزم لمس پیادهسازی قوانین یا خود Bazel برای معرفی موازیسازی بیشتر است. این همچنین می تواند زمانی اتفاق بیفتد که مقدار غیرعادی GC وجود داشته باشد.
فرمت فایل پروفایل
شی سطح بالا حاوی ابرداده ( otherData
) و داده ردیابی واقعی ( traceEvents
) است. فراداده حاوی اطلاعات اضافی است، برای مثال شناسه فراخوانی و تاریخ فراخوانی Bazel.
مثال:
{
"otherData": {
"build_id": "101bff9a-7243-4c1a-8503-9dc6ae4c3b05",
"date": "Tue Jun 16 08:30:21 CEST 2020",
"output_base": "/usr/local/google/_bazel_johndoe/573d4be77eaa72b91a3dfaa497bf8cd0"
},
"traceEvents": [
{"name":"thread_name","ph":"M","pid":1,"tid":0,"args":{"name":"Critical Path"}},
{"cat":"build phase marker","name":"Launch Bazel","ph":"X","ts":-1824000,"dur":1824000,"pid":1,"tid":60},
...
{"cat":"general information","name":"NoSpawnCacheModule.beforeCommand","ph":"X","ts":116461,"dur":419,"pid":1,"tid":60},
...
{"cat":"package creation","name":"src","ph":"X","ts":279844,"dur":15479,"pid":1,"tid":838},
...
{"name":"thread_name","ph":"M","pid":1,"tid":11,"args":{"name":"Service Thread"}},
{"cat":"gc notification","name":"minor GC","ph":"X","ts":334626,"dur":13000,"pid":1,"tid":11},
...
{"cat":"action processing","name":"Compiling third_party/grpc/src/core/lib/transport/status_conversion.cc","ph":"X","ts":12630845,"dur":136644,"pid":1,"tid":1546}
]
}
مهرهای زمانی ( ts
) و مدتها ( dur
) در رویدادهای ردیابی در میکروثانیه داده میشوند. دسته ( cat
) یکی از مقادیر enum ProfilerTask
است. توجه داشته باشید که برخی از رویدادها اگر بسیار کوتاه و نزدیک به یکدیگر باشند با هم ادغام می شوند. اگر میخواهید از ادغام رویداد جلوگیری کنید، --noslim_json_profile
را پاس کنید.
همچنین به مشخصات قالب رویداد ردیابی Chrome مراجعه کنید.
تجزیه و تحلیل-پروفایل
این روش پروفایل شامل دو مرحله است، ابتدا باید ساخت/تست خود را برای مثال با پرچم --profile
اجرا کنید.
$ bazel build --profile=/tmp/prof //path/to:target
فایل تولید شده (در این مورد /tmp/prof
) یک فایل باینری است که می توان آن را پس از پردازش و با دستور analyze-profile
کرد:
$ bazel analyze-profile /tmp/prof
به طور پیش فرض، اطلاعات تجزیه و تحلیل خلاصه را برای فایل داده پروفایل مشخص شده چاپ می کند. این شامل آمار تجمعی برای انواع وظایف مختلف برای هر مرحله ساخت و تجزیه و تحلیل مسیر بحرانی است.
بخش اول خروجی پیش فرض مروری بر زمان صرف شده در مراحل مختلف ساخت است:
INFO: Profile created on Tue Jun 16 08:59:40 CEST 2020, build ID: 0589419c-738b-4676-a374-18f7bbc7ac23, output base: /home/johndoe/.cache/bazel/_bazel_johndoe/d8eb7a85967b22409442664d380222c0
=== PHASE SUMMARY INFORMATION ===
Total launch phase time 1.070 s 12.95%
Total init phase time 0.299 s 3.62%
Total loading phase time 0.878 s 10.64%
Total analysis phase time 1.319 s 15.98%
Total preparation phase time 0.047 s 0.57%
Total execution phase time 4.629 s 56.05%
Total finish phase time 0.014 s 0.18%
------------------------------------------------
Total run time 8.260 s 100.00%
Critical path (4.245 s):
Time Percentage Description
8.85 ms 0.21% _Ccompiler_Udeps for @local_config_cc// compiler_deps
3.839 s 90.44% action 'Compiling external/com_google_protobuf/src/google/protobuf/compiler/php/php_generator.cc [for host]'
270 ms 6.36% action 'Linking external/com_google_protobuf/protoc [for host]'
0.25 ms 0.01% runfiles for @com_google_protobuf// protoc
126 ms 2.97% action 'ProtoCompile external/com_google_protobuf/python/google/protobuf/compiler/plugin_pb2.py'
0.96 ms 0.02% runfiles for //tools/aquery_differ aquery_differ
پروفایل حافظه
Bazel دارای یک نمایه کننده حافظه داخلی است که می تواند به شما کمک کند میزان استفاده از حافظه قانون خود را بررسی کنید. اگر مشکلی وجود دارد، میتوانید پشته را تخلیه کنید تا خط کد دقیقی را پیدا کنید که باعث ایجاد مشکل شده است.
فعال کردن ردیابی حافظه
شما باید این دو پرچم راه اندازی را به هر فراخوانی Bazel ارسال کنید:
STARTUP_FLAGS=\
--host_jvm_args=-javaagent:$(BAZEL)/third_party/allocation_instrumenter/java-allocation-instrumenter-3.3.0.jar \
--host_jvm_args=-DRULE_MEMORY_TRACKER=1
اینها سرور را در حالت ردیابی حافظه راه اندازی می کنند. اگر حتی برای یک فراخوانی Bazel این موارد را فراموش کنید، سرور دوباره راه اندازی می شود و شما باید از نو شروع کنید.
با استفاده از ردیاب حافظه
به عنوان مثال، به foo
هدف نگاه کنید و ببینید چه کاری انجام می دهد. برای اینکه فقط تجزیه و تحلیل را اجرا کنید و مرحله اجرای ساخت را اجرا نکنید، پرچم --nobuild
را اضافه کنید.
$ bazel $(STARTUP_FLAGS) build --nobuild //foo:foo
بعد، ببینید کل نمونه Bazel چقدر حافظه مصرف می کند:
$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc
> 2594MB
با استفاده از 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)
با تولید یک فایل pprof
با استفاده از bazel dump --skylark_memory
، نگاه کنید که حافظه به کجا می رود:
$ bazel $(STARTUP_FLAGS) dump --skylark_memory=$HOME/prof.gz
> Dumping Starlark heap to: /usr/local/google/home/$USER/prof.gz
از ابزار pprof
برای بررسی هیپ استفاده کنید. یک نقطه شروع خوب، گرفتن نمودار شعله با استفاده از pprof -flame $HOME/prof.gz
است.
pprof
از https://github.com/google/pprof دریافت کنید.
دریافت متنی از داغترین سایت های تماس مشروح با خطوط:
$ 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)