Gradle 플러그인이 구성 시간에 미치는 영향 감소: 여정

13151 단어 gradleperformance
콘카스트에 있는 한 친구가 최근에 나에게 메시지를 보냈다.

Hey Tony, I just noticed the dependency-analysis plugin is almost doubling our config time (adding ~3sec). Didn't see any open issues that would potentially be related to this. Should I open one up or do you think we might be doing something funky in our config or is this just a fact of life?


😱
나는 그에게 문제를 하나 만들라고 했다. 1 but undercut my own process by immediately following up and also asking if he could provide a profile. I've had optimizing my code with the use of profiles, and find them to be invaluable tools.

요점을 제시하다


본고에서 우리는 최적화 구축 성능이 통상적으로 일직선이 아니라 굴절된 과정을 보게 될 것이다.Gradle의 최악의 실천을 살펴보고, 이를 최선의 실천으로 대체하고, CPU 프로필과 구축 스캔을 지도할 것입니다.구축 프로세스의 CPU 프로필을 만드는 방법을 배울 것입니다(쉽습니다!)그것들을 어떻게 해석하는지 배워라.

스캔 생성부터


Kyle은 먼저 build scan을 제공했고 그 중에서 다음과 같은 내용을 발췌했습니다.

네, 엉망으로 보여요.나는 설정 시간이 3초밖에 안 되는 프로젝트에 종사하고 싶지 않다.
구축 스캔은 현재 벌어지고 있는 일을 보여줄 수 있지만, 그것이 무엇인지 이해할 수 있는 충분한 세부 사항을 제공하지 못했다.그렇게 많은 시간을 들여 무엇을 했습니까?왜 이 작업을 만듭니까?나는 내가 task configuration avoidance만 쓴 줄 알았어...

Narrator: he didn't.


봐라, my plugin은 안드로이드 프로젝트의 모든 변체 등록 임무를 통해 일한다. 2 and then also registering an aggregating task that combines all the variant-specific data into a single coherent whole. That holistic information is then the basis of the holistic "advice" it provides to users. And here's where it gets interesting. When I originally wrote the aggregating code, I was focused on getting it to work, and also apparently suffered a mild stroke?, because I completely ignored best practices and had done this:
val adviceTasks = tasks.withType<AdviceTask>()
val dependencyOutputs = mutableListOf<RegularFileProperty>()
adviceTasks.all {
  dependencyOutputs.add(adviceReport)
}

val aggregateAdviceTask = 
  tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice") {
    // a ListProperty<RegularFileProperty>
    dependencyAdvice.set(dependencyOutputs) 
  }

😱

For anyone who may not yet have gone through their ritual Gradle hazing, the issue here is tasks.withType<AdviceTask>().all { ... } , which eagerly realizes all tasks of type AdviceTask , defeating configuration avoidance.

I actually didn't notice this immediately after speaking with Kyle. At that point, I was waiting on the CPU profile he had promised, and so was working on something totally unrelated. But I had his issue in the back of my mind, tickling my neurons. When I stumbled across that snippet above, and finally saw what I had done (and then wept), I fixed it like so:

val aggregateAdviceTask
  = tasks.register<AdviceSubprojectAggregationTask>("aggregateAdvice")

val adviceTask = ...
aggregateAdviceTask.configure {
  // a ListProperty<RegularFile>
  dependencyAdvice.add(adviceTask.map { it.adviceReport })
}
(The commit is available here)
다른 점은 내가 즉시 집합 임무를 등록하고 구성 블록이 없다는 것이다.그리고 "sub"또는 변수가 특정한 작업을 추가할 때마다 단락별로 설정합니다.나는 완전한 단말기부터 단말기까지의 테스트 세트가 있기 때문에, 나는 모든 테스트를 실행함으로써 내가 어떤 것도 파괴하지 않았다는 것을 검증할 수 있다✅
나는 그 버전의 플러그인을 발표하고 켈에게 말했다.
그는 이것이 결코 그의 문제를 해결하지 못했다고 대답했다.

흥미롭다나의 플러그인은 더 이상 모든 작업을 급하게 실현하지 않지만, 설정은 여전히 3초가 걸린다.그리고 Kyle과 저는 임무 설정이 피하는 가치에 대해 간략하게 토론했습니다. 3 but that's neither here nor there. It was clear we had to go deeper. I asked again for a CPU profile.

CPU 구성 파일 계속


개인 정보에 대해 이야기할 것이 하나 있다.나는 그 중의 한 부분을 중점적으로 소개할 것이다. (당신의 개인 자료를 어떻게 만드는지에 대한 설명을 계속 읽어 주십시오.)
AndroidAppAnalyzercom.android.application 프로젝트를 분석하는 코드입니다.CPU 사용률 50%를 담당합니다!프로필에서 알 수 있듯이 이것은 registerClassAnalysisTask() 때문이다. 이것은 반대로 약간의 - 명백한 - 상당히 비싼 계산이 필요하다.다음은 문제 코드입니다.
val javaVariantFiles = androidSourceSets.flatMapToSet { sourceSet -> 
  project.files(sourceSet.javaDirectories)
    .asFileTree
    .files
    .toVariantFiles(sourceSet.name)
}
이 코드는 다음과 같은 작업을 수행합니다.
  • 정변체
  • 에 대한 안드로이드 소스 집합(Set<SourceProvider>)
  • 소스 세트당
  • 의 Java 디렉토리 가져오기
  • 디렉터리당 FileTree(디렉터리가 아닌 파일을 가리키는 것)
  • 파일 트리를 Set<File>으로 가져오기
  • 을 사용자 정의 데이터 클래스 VariantFile으로 변환합니다. 정의는 다음과 같습니다.
  • /**
     * Associates a [variant] ("main", "debug", "release", ...) with a 
     * [filePath] (to a file such as Java, Kotlin, or XML).
     */
    data class VariantFile(
      val variant: String,
      val filePath: String
    )
    
    (플러그인이 Android 프로젝트에 대해 변형된 특정 제안을 제공할 수 있는 일부 이유입니다.)
    마지막 두 단계(.files.toVariantFiles())는 각각 전체 원가의 35%와 10%를 차지하는 비싼 위치이다.
    나는 이 문제를 해결하기 위해 새로운 작업을 추가할 수 있으며, 현재 ClassListAnalysisTask을 설정하는 데 훨씬 낮은 비용으로 새 작업의 출력을 입력할 수 있다.이것은 입니다.
    이것은 새 CPU 구성 파일입니다.
    the commit
    총 CPU 시간의 50%에서 7% 이하로 감소!좋아.설정 시간도 6초 남짓에서 3초 남짓으로 떨어졌다.(응, 그들이 구축한 모든 문제가 내 플러그인에서 나온 것은 아니야!)

    간주


    내가 설정을 조정할 때 코드가 임무 수행 기간에 적응하도록 노력했을 때, Kyle은 계속 구축 스캔을 통해 조사를 진행했다.그는 알아차렸다.

    이것은 사용자 정의 스크립트 appcenter.gradle에서 온 것입니다. 이 스크립트는 응용 프로그램의 설정을 위해 발표됩니다.그 자체로 말하자면 1초가 걸려야 응용할 수 있지만, 플러그인의 모든 작업을 포함하여 많은 작업의 절박한 실현을 촉발시켰다. 이것은 왜 나의 이전 복구가 도움이 되지 않았는지, 플러그인 설정 성능 자체의 가치를 높이기 위해 계속 노력하는 것을 설명한다.
    Kyle이 단지 이 스크립트를 하나의 실험으로 논평했을 때, 그는 이어서 보았다.

    그의 극본이 가장 가까운 원인임을 증명하다.그 대본 뭐 하는지 맞히고 싶어요?만약 네가 지체할 수 없다면 계속 읽어라.
    project.afterEvaluate {
        project.tasks.all { task ->
    
    또 그 빌어먹을 all이 모든 것을 망쳤다.그래서 나는 임무 배치가 낭비가 아니라는 것을 피하고 싶다. 4

    Gradle Profiler 분석을 사용하여 생성


    우선gradle 프로필러를 설치하는 방법에 대한 설명은 을 참고하십시오.만약 당신이 이미 이것을 설치했다면, 그것이 최신 것인지 확인하십시오.
    이제 구성 단계를 평가하는 방법은 말할 것도 없습니다.
    $ gradle-profiler —profile async-profiler --project-dir <root-dir-of-build> help
    
    여기에 퀘스트 help이 사용되었습니다.이 작업은 기본적인 도움말 텍스트를 컨트롤러에 출력하기만 하면 매우 빠르다.따라서 help은 일반적으로 설정 시간의 유용한 에이전트로 사용된다.완료되면 profile-out-N의 작업 디렉터리에서 출력을 생성합니다. 여기서 N은 1 기반 인덱스입니다.이 디렉터리에서 화염도, 고드름도, 관련 파일을 찾을 수 있습니다.
    즐기다

    꼬리표


    1 Just realized he never did. Tsk tsk.
    2 up For JVM projects, it also looks at "all the variants," which in practice is just "main."
    3 up At, like, an ontological level.
    4 up But what does it mean?

    좋은 웹페이지 즐겨찾기