Bazel의 병렬 평가 및 성과 증분 모델
데이터 모델
데이터 모델은 다음 항목으로 구성됩니다.
SkyValue
: 노드라고도 합니다.SkyValues
는 빌드 과정 중에 빌드된 모든 데이터와 빌드의 입력을 포함하는 변경할 수 없는 객체입니다. 예를 들어 입력 파일, 출력 파일, 대상, 구성된 대상이 있습니다.SkyKey
:SkyValue
을 참조하는 변경할 수 없는 짧은 이름입니다(예:FILECONTENTS:/tmp/foo
또는PACKAGE://foo
).SkyFunction
. 키 및 종속 노드를 기반으로 노드를 빌드합니다.- 노드 그래프 노드 간의 종속 항목 관계가 포함된 데이터 구조입니다.
Skyframe
. Bazel의 기반이 되는 증분 평가 프레임워크의 코드 이름입니다.
평가
빌드는 빌드 요청을 나타내는 노드 평가로 구성됩니다 (이를 위해 노력 중인 상태이지만 그 과정에서 많은 기존 코드가 있습니다). 먼저 최상위 SkyKey
의 키로 SkyFunction
를 찾아 호출합니다. 그런 다음 이 함수는 최상위 노드를 평가하는 데 필요한 노드 평가를 요청하고, 결과적으로 리프 노드가 도달할 때까지 (일반적으로 파일 시스템의 입력 파일을 나타내는 노드임) 다른 함수 호출 등이 발생합니다. 마지막으로 최상위 SkyValue
의 값, 일부 부수 효과 (예: 파일 시스템의 출력 파일), 빌드와 관련된 노드 간 종속 항목의 방향성 비순환 그래프가 생성됩니다.
SkyFunction
는 작업을 실행하는 데 필요한 모든 노드를 미리 알 수 없는 경우 여러 패스에서 SkyKeys
를 요청할 수 있습니다. 간단한 예는 심볼릭 링크로 판명되는 입력 파일 노드를 평가하는 것입니다. 함수는 파일을 읽으려고 시도하고, 심볼릭 링크임을 인식하고, 심볼릭 링크의 타겟을 나타내는 파일 시스템 노드를 가져옵니다. 하지만 그 자체가 심볼릭 링크일 수 있으며, 이 경우 원래 함수도 대상을 가져와야 합니다.
함수는 코드에서 SkyFunction
인터페이스로 표현되고, 서비스에서 제공되는 서비스는 SkyFunction.Environment
라는 인터페이스로 표현됩니다. 함수로 할 수 있는 작업은 다음과 같습니다.
env.getValue
를 호출하여 다른 노드의 평가를 요청합니다. 노드를 사용할 수 있으면 노드 값이 반환되고, 그렇지 않으면null
가 반환되며 함수 자체가null
를 반환합니다. 후자의 경우 종속 노드가 평가된 후 원래 노드 빌더가 다시 호출되지만 이번에는 동일한env.getValue
호출이null
이 아닌 값을 반환합니다.env.getValues()
를 호출하여 다른 여러 노드의 평가를 요청합니다. 종속 노드가 동시에 평가된다는 점을 제외하고는 본질적으로 동일합니다.- 호출 중에 계산 수행
- 부수 효과(예: 파일 시스템에 파일 쓰기)가 있습니다. 두 가지 기능이 서로의 발가락 위에 올라가지 않도록 주의해야 합니다. 일반적으로 쓰기 부수 효과 (Bazel이 데이터 방향을 바깥쪽으로 흐르는 경우)는 괜찮지만 읽기 부작용 (등록된 종속 항목 없이 Bazel 안으로 데이터가 흐르는 경우)은 등록되지 않은 종속 항목이므로 잘못된 증분 빌드가 발생할 수 있으므로 허용되지 않습니다.
SkyFunction
구현은 종속 항목을 요청하는 것 이외의 방식 (예: 직접 파일 시스템 읽기)으로 데이터에 액세스해서는 안 됩니다. Bazel이 읽은 파일에 데이터 종속 항목을 등록하지 않아서 증분 빌드가 잘못되기 때문입니다.
함수가 작업을 수행하기에 충분한 데이터를 확보하면 완료를 나타내는 null
가 아닌 값을 반환해야 합니다.
이 평가 전략에는 다음과 같은 많은 이점이 있습니다.
- 밀폐성. 함수가 다른 노드에 따라 입력 데이터만 요청하는 경우 Bazel은 입력 상태가 동일한 경우 동일한 데이터가 반환되도록 보장할 수 있습니다. 모든 스카이 함수가 확정적이면 전체 빌드도 확정적입니다.
- 정확하고 완벽한 성과 증분입니다. 모든 함수의 입력 데이터가 모두 기록되면 Bazel은 입력 데이터가 변경될 때 무효화되어야 하는 정확한 노드 집합만 무효화할 수 있습니다.
- 동시 로드. 함수는 종속 항목을 요청하는 방식으로만 서로 상호 작용할 수 있으므로 서로 종속되지 않는 함수를 동시에 실행할 수 있으며 Bazel은 결과가 순차적으로 실행되는 경우와 같도록 보장할 수 있습니다.
Incrementality
함수는 다른 노드에 따라서만 입력 데이터에 액세스할 수 있으므로 Bazel은 입력 파일에서 출력 파일로 완전한 데이터 흐름 그래프를 작성하고, 이 정보를 사용하여 실제로 다시 빌드해야 하는 노드만 다시 빌드합니다. 즉, 변경된 입력 파일 세트의 역전이적 클로저입니다.
특히 상향식 전략과 하향식 전략에 대해 살펴보겠습니다. 최적의 그래프는 종속 항목 그래프의 모양에 따라 다릅니다.
상향식 무효화에서 그래프가 빌드되고 변경된 입력 세트가 알려진 후에는 변경된 파일에 전이적으로 종속되는 모든 노드가 무효화됩니다. 동일한 최상위 노드가 다시 빌드되는 것을 알고 있는 경우에 가장 적합합니다. 상향식 무효화에서는 변경되었는지 확인하려면 이전 빌드의 모든 입력 파일에서
stat()
를 실행해야 합니다.inotify
또는 유사한 메커니즘을 사용하여 변경된 파일에 관해 알아보면 이를 개선할 수 있습니다.하향식 무효화 시 최상위 노드의 전이적 닫힘이 확인되며 전이적 닫기가 깨끗한 노드만 유지됩니다. 현재 노드 그래프가 크다는 것을 알고 있지만 다음 빌드에서 작은 하위 집합만 필요합니다. 상향식 무효화는 두 번째 빌드의 작은 그래프를 따라 이동하는 하향식 무효화와 달리 첫 번째 빌드의 큰 그래프를 무효화합니다.
현재 상향식 무효화만 수행합니다.
더 높은 증분성을 얻기 위해 변경 프루닝을 사용합니다. 노드가 무효화되었지만 다시 빌드할 때 새 값이 이전 값과 동일하다는 것을 알게 된 경우, 이 노드의 변경으로 인해 무효화된 노드가 '복구됨'입니다.
예를 들어 C++ 파일에서 주석을 변경하면 파일에서 생성된 .o
파일이 동일하므로 링커를 다시 호출하지 않아도 됩니다.
증분 링크 / 컴파일
이 모델의 주요 제한사항은 노드 무효화가 '전부 아니면 전무' 방식의 문제라는 것입니다. 종속 항목이 변경되면 종속 노드가 항상 처음부터 다시 빌드되며, 더 나은 알고리즘이 있더라도 변경사항에 따라 이전 노드 값을 변경하더라도 마찬가지입니다. 다음은 몇 가지 유용한 예입니다.
- 증분 연결
.jar
에서 단일.class
파일이 변경되면.jar
파일을 처음부터 다시 빌드하는 대신 이론적으로 수정할 수 있습니다.
현재 Bazel이 이러한 사항을 원칙적으로 지원하지 않는 이유 (점진적 링크 지원을 어느 정도 지원하지만 Skyframe 내에서 구현되지 않음)는 두 가지입니다. 성능상의 이득이 제한적이어서 변형의 결과가 클린 빌드의 결과와 동일하고 Google은 비트를 반복할 수 있는 빌드를 가치 있다고 확신하기가 어려웠습니다.
지금까지는 비용이 많이 드는 빌드 단계를 분해하고 이러한 방식으로 부분 재평가를 달성하는 것만으로도 항상 충분한 성능을 얻을 수 있었습니다. 즉, 앱의 모든 클래스를 여러 그룹으로 분할하고 별도로 덱싱하는 작업을 했습니다. 이렇게 하면 그룹의 클래스가 변경되지 않으면 덱싱을 다시 실행하지 않아도 됩니다.
Bazel 개념에 매핑
다음은 Bazel이 빌드를 수행하는 데 사용하는 일부 SkyFunction
구현에 대한 대략적인 개요입니다.
- FileStateValue로 표현됩니다.
lstat()
의 결과 또한 기존 파일의 경우 파일의 변경사항을 감지하기 위해 추가 정보를 계산합니다. 이는 스카이프레임 그래프의 최하위 수준 노드이며 종속 항목이 없습니다. - FileValue). 실제 콘텐츠 또는 확인된 파일의 경로를 중요하게 여기는 대상에 사용됩니다. 해당하는
FileStateValue
과 확인해야 하는 모든 심볼릭 링크에 따라 다릅니다. 예를 들어a/b
의FileValue
에는 확인된 경로a
와a/b
의 확인된 경로가 필요합니다. 일부 경우(예:srcs=glob(["*/*.java"])
와 같은 파일 시스템 glob 평가)의 콘텐츠가 실제로 필요하지 않기 때문에 이러한 구분이 중요합니다. - DirectoryListingValue로 표현됩니다. 기본적으로
readdir()
의 결과입니다. 디렉터리와 연결된FileValue
에 따라 다릅니다. - PackageValue). BUILD 파일의 파싱된 버전을 나타냅니다. 연결된
BUILD
파일의FileValue
에 종속되며 패키지의 glob을 해결하는 데 사용되는DirectoryListingValue
에도 전이적으로 종속됨 (BUILD
파일의 콘텐츠를 내부적으로 나타내는 데이터 구조) - ConfiguredTargetValue 대상을 분석하는 동안 생성된 작업 집합과 이 대상에 종속된 구성된 대상에 제공되는 정보의 튜플인 구성된 대상을 나타냅니다. 상응하는 타겟이 있는
PackageValue
, 직접 종속 항목의ConfiguredTargetValues
, 빌드 구성을 나타내는 특수 노드에 따라 다릅니다. - ArtifactValue입니다. 소스 또는 출력 아티팩트 등의 빌드 내 파일을 나타냅니다. 아티팩트는 파일과 거의 동일하며 빌드 단계의 실제 실행 중에 파일을 참조하는 데 사용됩니다. 소스 파일의 경우 연결된 노드의
FileValue
에 따라 달라지며, 출력 아티팩트의 경우 아티팩트를 생성하는 작업의ActionExecutionValue
에 종속됩니다. - ActionExecutionValue입니다. 작업 실행을 나타냅니다. 입력 파일의
ArtifactValues
에 따라 다릅니다. 현재 실행 중인 작업은 스카이 키 내에 포함되어 있습니다. 이는 스카이 키가 작아야 한다는 개념과 상충합니다. 이 불일치를 해결하기 위해 노력하고 있습니다 (Skyframe에서 실행 단계를 실행하지 않는 경우ActionExecutionValue
및ArtifactValue
는 사용되지 않음).