前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jetpack Compose 导致的编译劣化 | KCP 简介

Jetpack Compose 导致的编译劣化 | KCP 简介

作者头像
逮虾户
发布2022-09-02 11:49:48
1K0
发布2022-09-02 11:49:48
举报
文章被收录于专栏:逮虾户

前言

最近的更新频率很低,虽然有很大一部分原因是我在偷懒,另外是因为想从工作中的一些奇怪的问题出发,写一些可能对大家有帮助的内容。

最近从我们编译的均值数据上发现了编译时间有劣化的现象,然后我们在buildscan排查了下全量编译的情况下任务的耗时。发现个别模块的compileKotlin耗时明显变长了很多,这个奇怪的现象引起了我们的注意。另外这个仓库可能最近最大的改动点就是引入了compose,然后开发了个新的页面。所以我们初步怀疑可能就是由于compose导致的该问题。

验证环节

我们找到了这个模块的一个切片节点,接入compose之前和接入compose之后。因为担心单次编译对其产生的误差影响,我们用相同环境进行了10次的打包验证。

未开启compose情况下编译情况

image.png
image.png

开启compose情况下编译情况

image.png
image.png

我们对比下均值数据,可以明显发现开启compose前后的编译时长发生了明显的变化。所以足以得出结论compose会导致编译速度变慢,而且非常大也非常明显。而且平均耗时增加了1min30s左右。

kcp

KCPKotlin Compiler Plugin(Kotlin编译器插件),在 kotlinc 过程中提供 hook 时机,在此期间可以生成代码、修改字节码等。

其中我们很熟悉的kotlin-android-extensions就是一KCP插件,虽然他现在也已经废弃了。

image.png
image.png

Compose编译情况不同于别的的ksp,它需要深度的参与本次编译,然后修改当前kotlin类的编译产物。将dsl等等的语法信息进行转化。

所以当我们打开gradle内的compose的时候,其实也就相当于给kcp添加了个额外的编译插件。然后在kotlinCompiler的过程中修改当前我们写的compose相关的代码。

代码语言:javascript
复制
buildFeatures {
      compose true
  }
复制代码

所以从逻辑上来说,只要我们将当前模块开启compose之后就肯定会让这个模块的编译时间拉长。以下是我找了一个demo工程从0-1完成项目打包,然后对比了下kt代码和实际的class产物。

Sample工程地址 我用了一个大佬的

代码语言:javascript
复制
@Composable
fun SplashPage(onNextPage: () -> Unit) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(AppTheme.colors.themeUi), contentAlignment = Alignment.TopCenter
    ) {
        LaunchedEffect(Unit) {
            delay(500)
            onNextPage.invoke()
        }
        Text(
            text = "Wan Android",
            fontSize = 32.sp,
            color = white,
            fontWeight = FontWeight.Bold,
            modifier = Modifier.padding(0.dp, 150.dp, 0.dp, 0.dp)
        )
    }
}
复制代码
代码语言:javascript
复制
/* compiled from: SplashPage.kt */
@Metadata(d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u001b\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00010\u0003H\u0007¢\u0006\u0002\u0010\u0004¨\u0006\u0005"}, d2 = {"SplashPage", "", "onNextPage", "Lkotlin/Function0;", "(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V", "app_debug"}, k = 2, mv = {1, 5, 1}, xi = 48)
/* loaded from: classes7.dex */
public final class SplashPageKt {
    public static final void SplashPage(Function0<Unit> onNextPage, Composer $composer, int $changed) {
        Object value$iv$iv;
        Intrinsics.checkNotNullParameter(onNextPage, "onNextPage");
        Composer $composer2 = $composer.startRestartGroup(1819087201);
        ComposerKt.sourceInformation($composer2, "C(SplashPage)23@817L6,20@724L481:SplashPage.kt#dcpdb2");
        int $dirty = $changed;
        if (($changed & 14) == 0) {
            $dirty |= $composer2.changed(onNextPage) ? 4 : 2;
        }
        if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
            Modifier modifier$iv = BackgroundKt.m110backgroundbw27NRU$default(SizeKt.fillMaxSize$default(Modifier.Companion, 0.0f, 1, null), AppTheme.INSTANCE.getColors($composer2, 0).m4383getThemeUi0d7_KjU(), null, 2, null);
            Alignment contentAlignment$iv = Alignment.Companion.getTopCenter();
            $composer2.startReplaceableGroup(-1990474327);
            ComposerKt.sourceInformation($composer2, "C(Box)P(2,1,3)70@3267L67,71@3339L130:Box.kt#2w3rfo");
            MeasurePolicy measurePolicy$iv = BoxKt.rememberBoxMeasurePolicy(contentAlignment$iv, false, $composer2, ((0 >> 3) & 14) | ((0 >> 3) & 112));
            int $changed$iv$iv = (0 << 3) & 112;
            $composer2.startReplaceableGroup(1376089335);
            ComposerKt.sourceInformation($composer2, "C(Layout)P(!1,2)71@2788L7,72@2843L7,73@2855L389:Layout.kt#80mrfh");
            ComposerKt.sourceInformationMarkerStart($composer2, 103361330, "C:CompositionLocal.kt#9igjgp");
            Object consume = $composer2.consume(CompositionLocalsKt.getLocalDensity());
            ComposerKt.sourceInformationMarkerEnd($composer2);
            Density density$iv$iv = (Density) consume;
            ComposerKt.sourceInformationMarkerStart($composer2, 103361330, "C:CompositionLocal.kt#9igjgp");
            Object consume2 = $composer2.consume(CompositionLocalsKt.getLocalLayoutDirection());
            ComposerKt.sourceInformationMarkerEnd($composer2);
            LayoutDirection layoutDirection$iv$iv = (LayoutDirection) consume2;
            Function0 factory$iv$iv$iv = ComposeUiNode.Companion.getConstructor();
            Function3 skippableUpdate$iv$iv$iv = LayoutKt.materializerOf(modifier$iv);
            int $changed$iv$iv$iv = ($changed$iv$iv << 9) & 7168;
            if (!($composer2.getApplier() instanceof Applier)) {
                ComposablesKt.invalidApplier();
            }
            $composer2.startReusableNode();
            if ($composer2.getInserting()) {
                $composer2.createNode(factory$iv$iv$iv);
            } else {
                $composer2.useNode();
            }
            $composer2.disableReusing();
            Composer $this$Layout_u24lambda_u2d0$iv$iv = Updater.m898constructorimpl($composer2);
            Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, measurePolicy$iv, ComposeUiNode.Companion.getSetMeasurePolicy());
            Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, density$iv$iv, ComposeUiNode.Companion.getSetDensity());
            Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, layoutDirection$iv$iv, ComposeUiNode.Companion.getSetLayoutDirection());
            $composer2.enableReusing();
            skippableUpdate$iv$iv$iv.invoke(SkippableUpdater.m889boximpl(SkippableUpdater.m890constructorimpl($composer2)), $composer2, Integer.valueOf(($changed$iv$iv$iv >> 3) & 112));
            $composer2.startReplaceableGroup(2058660585);
            int $changed$iv = ($changed$iv$iv$iv >> 9) & 14;
            $composer2.startReplaceableGroup(-1253629305);
            ComposerKt.sourceInformation($composer2, "C72@3384L9:Box.kt#2w3rfo");
            if ((($changed$iv & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
                BoxScopeInstance boxScopeInstance = BoxScopeInstance.INSTANCE;
                $composer2.startReplaceableGroup(1601293652);
                ComposerKt.sourceInformation($composer2, "C25@910L66,25@889L87,29@985L214:SplashPage.kt#dcpdb2");
                if ((((((0 >> 6) & 112) | 6) & 81) ^ 16) != 0 || !$composer2.getSkipping()) {
                    Unit unit = Unit.INSTANCE;
                    int i = $dirty & 14;
                    $composer2.startReplaceableGroup(-3686930);
                    ComposerKt.sourceInformation($composer2, "C(remember)P(1):Composables.kt#9igjgp");
                    boolean invalid$iv$iv = $composer2.changed(onNextPage);
                    Object it$iv$iv = $composer2.rememberedValue();
                    if (!invalid$iv$iv && it$iv$iv != Composer.Companion.getEmpty()) {
                        value$iv$iv = it$iv$iv;
                        $composer2.endReplaceableGroup();
                        EffectsKt.LaunchedEffect(unit, (Function2) value$iv$iv, $composer2, 0);
                        long sp = TextUnitKt.getSp(LiveLiterals$SplashPageKt.INSTANCE.m4958x266b4559());
                        long white = ColorKt.getWhite();
                        FontWeight bold = FontWeight.Companion.getBold();
                        int $this$dp$iv = LiveLiterals$SplashPageKt.INSTANCE.m4954x579b3509();
                        float f = Dp.m2966constructorimpl($this$dp$iv);
                        int $this$dp$iv2 = LiveLiterals$SplashPageKt.INSTANCE.m4955x709c86a8();
                        float f2 = Dp.m2966constructorimpl($this$dp$iv2);
                        int $this$dp$iv3 = LiveLiterals$SplashPageKt.INSTANCE.m4956x899dd847();
                        float f3 = Dp.m2966constructorimpl($this$dp$iv3);
                        int $this$dp$iv4 = LiveLiterals$SplashPageKt.INSTANCE.m4957xa29f29e6();
                        TextKt.m868TextfLXpl1I(LiveLiterals$SplashPageKt.INSTANCE.m4960x3de3a5a7(), PaddingKt.m283paddingqDBjuR0(Modifier.Companion, f, f2, f3, Dp.m2966constructorimpl($this$dp$iv4)), white, sp, null, bold, null, 0L, null, null, 0L, 0, false, 0, null, null, $composer2, 384, 64, 65488);
                    }
                    value$iv$iv = (Function2) new SplashPageKt$SplashPage$1$1$1(onNextPage, null);
                    $composer2.updateRememberedValue(value$iv$iv);
                    $composer2.endReplaceableGroup();
                    EffectsKt.LaunchedEffect(unit, (Function2) value$iv$iv, $composer2, 0);
                    long sp2 = TextUnitKt.getSp(LiveLiterals$SplashPageKt.INSTANCE.m4958x266b4559());
                    long white2 = ColorKt.getWhite();
                    FontWeight bold2 = FontWeight.Companion.getBold();
                    int $this$dp$iv5 = LiveLiterals$SplashPageKt.INSTANCE.m4954x579b3509();
                    float f4 = Dp.m2966constructorimpl($this$dp$iv5);
                    int $this$dp$iv22 = LiveLiterals$SplashPageKt.INSTANCE.m4955x709c86a8();
                    float f22 = Dp.m2966constructorimpl($this$dp$iv22);
                    int $this$dp$iv32 = LiveLiterals$SplashPageKt.INSTANCE.m4956x899dd847();
                    float f32 = Dp.m2966constructorimpl($this$dp$iv32);
                    int $this$dp$iv42 = LiveLiterals$SplashPageKt.INSTANCE.m4957xa29f29e6();
                    TextKt.m868TextfLXpl1I(LiveLiterals$SplashPageKt.INSTANCE.m4960x3de3a5a7(), PaddingKt.m283paddingqDBjuR0(Modifier.Companion, f4, f22, f32, Dp.m2966constructorimpl($this$dp$iv42)), white2, sp2, null, bold2, null, 0L, null, null, 0L, 0, false, 0, null, null, $composer2, 384, 64, 65488);
                } else {
                    $composer2.skipToGroupEnd();
                }
                $composer2.endReplaceableGroup();
            } else {
                $composer2.skipToGroupEnd();
            }
            $composer2.endReplaceableGroup();
            $composer2.endReplaceableGroup();
            $composer2.endNode();
            $composer2.endReplaceableGroup();
            $composer2.endReplaceableGroup();
        } else {
            $composer2.skipToGroupEnd();
        }
        ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
        if (endRestartGroup == null) {
            return;
        }
        endRestartGroup.updateScope(new SplashPageKt$SplashPage$2(onNextPage, $changed));
    }
}
复制代码

从上述可以发现,虽然是一段非常简单的compose写的逻辑ui,但是实际上编译出来的代码其实远比想象中的多得多,而且同样也会对调用点进行修改。因此导致编译速度变慢也就合情合理。

编译方面的抉择

我们定位到问题之后,就是如何选择优化了。如果各位大佬让我优化compose的编译速度的话,那么在下肯定无能为力了。

但是如果在我们之前的快编框架体系内的话,我们会选择将新增的compose相关的逻辑抽取出来,写成一个新的模块,然后将新旧代码进行隔离。因为对我们来说compose只是试水而已,他的改动可能并不会这么频繁.如果直接在原来的业务模块内添加compose的代码,因为本身模块都已经比较大了,然后又需要增加kotlinCompiler的时间,则会导致开发体验直线下降,大家都会说ci团队做的不好,导致编译速度变慢了。

一点点小看法

项目原先对于编译相关的监控投入的其实也并不多,大部分都要依靠与buildscan。我们也在尝试记录这部分相关的数据。和之前介绍过的一样,我们这次也是从BuildOperationNotificationListenerExecuteTaskBuildOperationDetails去获取编译任务耗时相关的数据。

这次没有考虑使用TaskExecutionListener,则还是因为复合构建的问题,由于复合构建每个gradle实例都是独立的,需要全部注册TaskExecutionListener,还有需要实例的同步,所以就有非常多的问题。

另外还写了个很挫的Top3耗时Project分析,哈哈哈。

代码语言:javascript
复制
if (notification.notificationOperationDetails is ExecuteTaskBuildOperationDetails) {
          try {
              val d: ExecuteTaskBuildOperationDetails =
                  notification.notificationOperationDetails as ExecuteTaskBuildOperationDetails
              val result: ExecuteTaskBuildOperationType.Result =
                  notification.notificationOperationResult as ExecuteTaskBuildOperationType.Result
              map[notification.notificationOperationId]?.also { get ->
                  val stringBuffer = StringBuffer()
                  val exTime =
                      notification.notificationOperationFinishedTimestamp - get.notificationOperationStartedTimestamp
                
                  if (result.originExecutionTime != null) {
                      result.originExecutionTime?.apply {
                          if (exTime > this) {
                              stringBuffer.append("!!!比上次cache任务多执行 " + TimeUtils.toDec(exTime - this))
                          } else {
                              stringBuffer.append("比上次cache任务减少执行 " + TimeUtils.toDec(this - exTime))
                          }
                      }
                      stringBuffer.append("\n")
                  }
                  val taskBuildData = d.create(exTime, stringBuffer.toString())
                  val taskKey = taskBuildData.getKey()
                  if (!values.containsKey(taskKey)) {
                      values[taskKey] = ModuleTasksData(taskKey)
                  }
                  values[taskKey]?.addTaskBuildData(taskBuildData)
              }
          } catch (t: Throwable) {
              t.printStackTrace()
          }
      }
复制代码

monitor 工程传送门

代码基本如下,因为在monitor下执行,就可以监控到所有工程的编译耗时了。

总结

我个人看法虽然compose的接入会对编译组产生一定的挑战,但是整体来说还是非常值得我们去尝试和挑战的一个技术。

加油各位打工人。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 验证环节
  • kcp
  • 编译方面的抉择
  • 一点点小看法
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档