규칙을 작성할 때 가장 일반적인 성능 문제는 순회 또는 복사입니다. 누적된 데이터입니다 전체 기간에 걸쳐 집계되는 경우 이러한 작업은 쉽게 O(N^2) 시간이나 공간을 취할 수 있습니다. 이를 방지하려면 depset을 효과적으로 사용하는 방법을 이해하는 데 매우 중요합니다.
제대로 하기 어려울 수 있으므로 Bazel은 메모리 프로파일러를 사용하여 실수를 한 부분을 찾는 데 도움이 됩니다. 주의: 비효율적인 규칙을 작성하는 데 드는 비용은 널리 사용됩니다.
depset 사용
규칙 종속 항목에서 정보를 롤업할 때마다 depsets 일반 목록 또는 사전을 사용하여 정보를 게시 로컬에 저장됩니다.
depset은 공유를 사용 설정하는 중첩 그래프로 정보를 나타냅니다.
다음 그래프를 살펴보세요.
C -> B -> A
D ---^
각 노드는 단일 문자열을 게시합니다. depset를 사용하면 데이터는 다음과 같습니다.
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'
가 4번 언급됩니다. 그래프가 크면
문제는 더욱 악화될 것입니다
다음은 전이 정보를 게시합니다. 규칙 로컬에 게시해도 괜찮습니다. 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()
호출 방지
다음 명령어를 사용하여 depset를 평면 목록으로 강제 변환할 수 있습니다.
to_list()
를 사용하지만 그렇게 하면 일반적으로 O(N^2)가 됩니다.
있습니다. 가능하면 디버깅을 제외한 depset의 평면화를 피합니다.
있습니다.
일반적인 오해는
비용이 <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% 이상이 될 수 있습니다.
다음은 몇 가지 유용한 정보입니다.
depset 및 목록을 평면화하지 않고 인수로 직접 전달 확인할 수 있습니다
ctx.actions.args()
까지 확장됩니다. depset 콘텐츠에 변환이 필요한 경우 ctx.actions.args#add하여 청구 금액이 있는지 확인합니다.File#path
을 인수로 전달하나요? 그럴 필요가 없습니다. 모든 문자 파일은 path, 확장 시간까지 지연됨.문자열을 함께 연결하여 문자열을 구성하지 마세요. 최적의 문자열 인수는 메모리가 서로 공유되므로 상수입니다. 규칙을 만들 수 있습니다.
인수가 명령줄에 비해
ctx.actions.args()
객체에 비해 너무 긴 경우 는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
임시 작업 입력은 분리해야 합니다.
ctx.actions.run을 사용하여 작업을 빌드할 때는
inputs
필드가 depset을 허용한다는 것을 기억하세요. 입력이
종속 항목에서 전이적으로 수집됩니다
inputs = depset(...)
ctx.actions.run(
inputs = inputs, # Do *not* turn inputs into a list
...
)
걸이식
Bazel이 걸려있는 것 같으면 Ctrl-\를 누르거나
SIGQUIT
신호 (kill -3 $(bazel info server_pid)
)를 사용하여 스레드를 가져옵니다.
$(bazel info output_base)/server/jvm.out
파일에 덤프합니다.
bazel이 걸려 있으면 bazel info
를 실행하지 못할 수 있으므로
일반적으로 output_base
디렉터리는 bazel-<workspace>
의 상위 요소입니다.
symlink를 삭제합니다.
성능 프로파일링
Bazel은 다음과 같이 출력 베이스의 command.profile.gz
에 JSON 프로필을 작성합니다.
기본값입니다. 다음 명령어로 위치를 구성할 수 있습니다.
--profile
플래그의 예:
--profile=/tmp/profile.gz
입니다. 마지막 자리가 .gz
인 위치는 다음으로 압축됩니다.
GZIP.
결과를 보려면 Chrome 브라우저 탭에서 chrome://tracing
앱을 열고 다음을 클릭하세요.
"로드" (압축될 수 있는) 프로필 파일을 선택합니다. 자세한 내용은
왼쪽 하단의 상자를 클릭합니다.
다음 키보드 컨트롤을 사용하여 탐색할 수 있습니다.
- '선택'하려면
1
키를 누르세요. 있습니다. 이 모드에서는 특정 상자를 클릭해 이벤트 세부정보를 확인합니다 (왼쪽 하단 참조). 여러 이벤트를 선택하여 요약 및 집계된 통계를 확인합니다. - '화면 이동'하려면
2
키를 누르세요. 있습니다. 그런 다음 마우스를 드래그하여 보기를 이동합니다. 나a
/d
를 사용하여 왼쪽/오른쪽으로 이동할 수도 있습니다. - '확대/축소'하려면
3
키를 누르세요. 있습니다. 그런 다음 마우스를 드래그하여 확대/축소합니다. 다음과 같은 작업을 할 수 있습니다.w
/s
를 사용하여 확대/축소할 수도 있습니다. - '타이밍'을 선택하려면
4
키를 누르세요. 거리를 측정할 수 있는 모드입니다. 확인할 수 있습니다. ?
키를 눌러 모든 컨트롤에 대해 알아보세요.
프로필 정보
프로필 예:
그림 1. 프로필 예
다음과 같은 몇 가지 특수 행이 있습니다.
action counters
: 진행 중인 동시 작업 수를 표시합니다. 클릭 실제 값을 확인할 수 있습니다.--jobs
값까지 있어야 함: 클린 빌드를 만듭니다.cpu counters
: 빌드의 초당 CPU 용량을 표시합니다. (값 1은 코어 1개가 100% 사용 중인 것과 같음).Critical Path
: 중요 경로의 각 작업에 대해 하나의 블록을 표시합니다.grpc-command-1
: Bazel의 기본 스레드입니다. 개괄적인 그림을 그리는 데 유용합니다. 'Launch Bazel', 'evaluateTargetPatterns' 등 Bazel이 수행하는 작업 'runAnalysisPhase'가 포함됩니다.Service Thread
: 소규모 및 주요 가비지 컬렉션 (GC) 일시중지를 표시합니다.
다른 행은 Bazel 스레드를 나타내며 해당 스레드의 모든 이벤트를 표시합니다.
일반적인 성능 문제
성능 프로필을 분석할 때는 다음을 확인하세요.
- 예상보다 느린 분석 단계 (
runAnalysisPhase
), 특히 다음에서 느림 빌드됩니다 이는 잘못된 규칙 구현의 징후일 수 있습니다. depset을 평면화하는 예 패키지 로드 속도가 과도한 양의 타겟, 복잡한 매크로 또는 재귀 glob 등이 있습니다. - 개별 느린 작업, 특히 중요한 경로에 있는 작업입니다. 그것은
큰 작업을 여러 개의 작은 작업으로 분할하거나
종속 항목 집합을 사용하여 속도를 높입니다. 또한 비정상적인
PROCESS_TIME
외의 높은 값 (예:REMOTE_SETUP
또는FETCH
) - 병목 현상, 즉 소수의 스레드는 바쁘지만 나머지는 모두 결과를 기다리는 중입니다 (위 스크린샷에서 약 15초~30초 참고). 이를 최적화하려면 규칙 구현을 수정해야 할 가능성이 높습니다. Bazel 자체에서 더 많은 동시 로드를 도입할 수 있습니다 이 문제는 GC의 양이 비정상적으로 많습니다.
프로필 파일 형식
최상위 객체에는 메타데이터 (otherData
)와 실제 추적 데이터가 포함되어 있습니다.
(traceEvents
). 메타데이터에 추가 정보(예: 호출 ID)가 포함되어 있습니다.
확인할 수 있습니다
예:
{
"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
)는 ProfilerTask
의 열거형 값 중 하나입니다.
일부 이벤트는 매우 짧고 가까운 거리에 있으면 병합됩니다.
서로 다음을 전달하려면 --noslim_json_profile
를 전달하세요.
이벤트 병합을 방지할 수 있습니다.
자세한 내용은 Chrome 트레이스 이벤트 형식 사양.
analyze-profile
이 프로파일링 방법은 두 단계로 구성됩니다. 먼저
--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
를 사용하여 Flame 그래프를 가져옵니다.
https://github.com/google/pprof에서 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)