[Android] 스타카토 Lazy Column 성능 약 95% 개선기 2편 (Benchmark & Perfetto)

hxeyexn·2025년 8월 3일
post-thumbnail

들어가기 전

이번 시리즈는 총 2편으로 구성되어 있습니다.

1편에서는 성능 측정을 위한 설정 과정과 분석 방법을 다룹니다.
2편에서는 분석 결과를 바탕으로 LazyColumn의 성능을 개선하기 위해 시도한 내용과 그 결과를 소개합니다.

목차

  • Intro
  • 기존 Lazy Column 성능 이슈 파악
    • 성능 측정을 위한 Benchmark 코드 작성
    • 성능 분석
  • 유의미했던 성능 개선 시도
    • Icon SVG를 XML이 아닌 Kotlin 파일로 변경
    • ConstraintLayout을 Row/Column + Slot 패턴으로 변경
  • 성능 개선 결과
  • 성능 개선을 하면서 든 고민
    • Baseline Profile 도입 여부
    • Benchmark를 위한 CategoriesBenchmarkActivity의 위치
    • 참여자들 LazyRow -> Row 변경 여부
  • Outro


Intro

지난 시간에는 LazyColumn 스크롤 버벅거림의 원인을 찾기 위해 먼저 성능 측정 환경을 구축했다. 그리고 Benchmark와 Perfetto를 함께 사용해야 성능을 보다 정확하게 분석할 수 있다는 점도 확인했다. 이번 편에서는 본격적으로 성능을 측정한 뒤, 이를 바탕으로 다양한 개선 시도를 통해 스크롤 성능을 최적화해 보겠다.

참고로 성능을 개선하려는 LazyColumn의 아이템은 아래와 같은 UI로 구성되어 있다.




기존 LazyColumn 성능 이슈 파악

성능 측정을 위한 Benchmark 코드 작성

성능 측정을 시작하기 전에 먼저 Benchmark 테스트 코드에 몇 가지 기본 설정을 해줘야 한다. 이 설정과 관련된 기본 지식(CompilationMode, StartupMode, JIT, AOT 등)은 1편에서 이미 다뤘기 때문에 이번 편에서 자세하게 다루지는 않을 것이다. 코드를 먼저 살펴보고 하나씩 어떤 식으로 설정하는지 알아보자.

@RunWith(AndroidJUnit4::class)
class CategoriesScrollBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun scrollCategoriesCompilationFull() = scrollCategories(CompilationMode.Full())

    private fun scrollCategories(compilationMode: CompilationMode) =
        benchmarkRule.measureRepeated(
            packageName = "com.on.staccato",
            compilationMode = compilationMode,
            metrics =
                listOf(
                    FrameTimingMetric(),
                    TraceSectionMetric("CategoryItem"),
                    ...
                ),
            iterations = 5,
            startupMode = StartupMode.WARM,
            setupBlock = {
		            pressHome()
                val intent =
                    Intent().apply {
                        setClassName(
                            "com.on.staccato",
                            "com.on.staccato.benchmark.CategoriesBenchmarkActivity",
                        )
                    }
                startActivityAndWait(intent)
            },
        ) {
            ...
        }
}

Android Espresso로 UI 테스트를 작성해 본 경험이 있다면, 비교적 아래 코드가 익숙하게 느껴질 것이다.

Jetpack Macrobenchmark는 MacrobenchmarkRule의 JUnit4 Rule API를 통해 손쉽게 성능 테스트를 작성할 수 있도록 지원한다.

@RunWith(AndroidJUnit4::class)
class CategoriesScrollBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()
		...
}

measureRepeated(...) 는 지정된 packageName에 대해 주어진 metrics를 기반으로 동작을 측정한다. 이제 이 메서드에 전달할 매개변수를 하나씩 살펴보자.

  1. packageName 설정
    프로젝트의 패키지 이름을 작성하면 된다.

    benchmarkRule.measureRepeated(
        packageName = "com.on.staccato",
        ...
    )

  2. compilationMode 설정
    LazyColumn의 스크롤 성능은 런타임 성능과 밀접한 관련이 있으므로 Full 모드를 사용할 것이다. Full 모드는 앱의 전체 코드를 사전에 컴파일해 실행 시 JIT(Just-In-Time) 모드에서 동작하는 코드가 없도록 해준다.

    benchmarkRule.measureRepeated(
        ...
            // 필자는 외부에서 CompilationMode.Full() 주입
        compilationMode = compilationMode, 
    )

  3. metrics 설정
    벤치마크에서 측정할 정보의 유형을 지정하는 매개변수이다. 필자는 프레임 렌더링 시간을 측정하기 위해 FrameTimingMetric()을 사용해 frameDurationCpuMs와 frameOverrunMs를 측정할 것이다. 지난 1편에서 두 지표에 대해 알아보았지만, 다시 한번 짚고 넘어가자.

    • frameDurationCpuMs
      • 프레임을 렌더링하는 데 드는 시간을 알려줌
      • 짧을수록 좋음
    • frameOverrunMs
      • GPU 작업을 비롯해 프레임 제한을 초과한 시간을 알려줌
      • 이 값은 양수일 수도, 음수일 수도 있음
      • 음수가 좋음 → 프레임을 생성하는 데 시간이 남았다는 뜻이기 때문
    benchmarkRule.measureRepeated(
        ...
        metrics =
            listOf(
                FrameTimingMetric(),
                            ...
            ),
    )

    또한 TraceSectionMetric(sectionName = ...)을 사용하면, 지정한 sectionName을 가진 코드 블록의 시작과 끝을 기록해 해당 구간의 실행 시간을 측정할 수 있다.

    아래와 같이 trace 함수를 작성한 뒤, 실행 시간을 측정하려는 코드 블록을 이 함수로 감싸고 sectionName을 인자로 전달하면 된다.

    import android.os.Trace
    
    inline fun <T> trace(
        sectionName: String,
        block: () -> T,
    ): T {
        Trace.beginSection(sectionName)
        try {
            return block()
        } finally {
            Trace.endSection()
        }
    }
    @Composable
    fun Categories(
        ...
    ) {
        ...
        LazyColumn(
            modifier = modifier.testTag("categories_lazy_column"),
            state = lazyListState,
        ) {
            items(
                items = categories,
                key = { it.categoryId },
            ) { category ->
                trace("CategoryItem") {
                    CategoryItem(
                        category = category,
                        onCategoryClicked = onCategoryClicked,
                    )
                }
                DefaultDivider()
            }
        }  
    }

    그리고 Benchmark 테스트에서는 TraceSectionMetric(...)trace와 동일한 sectionName을 전달해, 해당 구간의 metric을 수집할 수 있다.

    benchmarkRule.measureRepeated(
        ...
        metrics =
            listOf(
                FrameTimingMetric(),
                TraceSectionMetric("CategoryItem"),
                ...
            ),
    )

  4. iterations 설정

    iterations은 벤치마크를 몇 번 반복할지 결정하는 매개 변수다. 반복 횟수가 많을수록 결과의 안정성이 높아지지만, 실행 시간이 더 오래 걸리는 단점이 있다. 공식 문서에서는 약 10회를 권장하지만 10으로 설정했을 때 실행 시간이 지나치게 길어지고, 안드로이드 스튜디오가 멈추거나 노트북이 과열되는 문제가 발생했다. 여러 차례 테스트해 본 결과, 5회만 설정해도 결과가 충분히 안정적이어서 필자는 5회로 설정했다.

    benchmarkRule.measureRepeated(
        ...
        iterations = 5,
    )

  5. startupMode 설정

    Android Docs | Write a Macrobenchmark - StartupMode
    Warning: If StartupMode.COLD is used, the app process is killed between the execution of setupBlock and measureBlock to allow for app preparation without starting the process. If you need the process to remain active, use StartupMode.WARM, which restarts activities without restarting the process, or set startupMode to null and call killProcess() within the setupBlock.

    공식문서에 따르면 프로세스를 활성 상태로 유지해야 하는 경우, 프로세스를 재시작하지 않고 액티비티를 다시 시작하는 StartupMode.WARM을 사용을 권장한다. 따라서 LazyColumn의 스크롤 성능을 보다 정확하게 측정하기 위해서는 Warm 모드를 사용하는 것이 적합하다.

    benchmarkRule.measureRepeated(
        ...
        startupMode = StartupMode.WARM,
    )

  6. setupBlock

    setupBlock은 성능 측정을 시작하기 전에, 측정 대상 UI 화면으로 이동하는 작업을 수행하는 블록이다. 이 블록에서 Intent를 사용해 테스트할 UI 화면을 실행하면 된다.

    스타카토의 경우 로그인 과정과 바텀시트로 인해 원하는 화면으로 바로 진입하기가 어려워, 별도의 Benchmark 전용 Activity를 만들어 사용했다.

    benchmarkRule.measureRepeated(
        ...
        setupBlock = {
            pressHome()
            val intent =
                Intent().setClassName(
                	// packageName
                  	"com.on.staccato",
                 	// className
                   	"com.on.staccato.benchmark.CategoriesBenchmarkActivity",
            	 )
            startActivityAndWait(intent)
        },
    )

  7. measureBlock
    iteration마다 벤치마크할 앱 동작을 수행하는 블록이다. 먼저 동작을 수행할 컴포넌트를 찾아야 한다. 측정 대상 컴포넌트의 modifiertestTag를 지정한다. testTag 값은 자유롭게 설정하면 된다. 이후 테스트 코드에서 Until.findObject를 사용해 testTag와 동일한 값을 가진 객체를 찾아 동작을 수행한다.

    @Composable
    fun Categories(
      ...
    ) {
    	...
    	LazyColumn(
        // frame 렌더링 시간을 측정하려는 컴포넌트의 modifier에 testTag 설정
    	    modifier = modifier.testTag("categories_lazy_column"),
    	    state = lazyListState,
    	) {
    	    ...
    	}  
    }

    필자는 스크롤 성능을 측정하기 위해 화면을 위아래로 두 번 정도 스크롤하는 동작을 설정했다.

    @RunWith(AndroidJUnit4::class)
    class CategoriesScrollBenchmark {
        @get:Rule
        val benchmarkRule = MacrobenchmarkRule()
    
        @Test
        fun scrollCategoriesCompilationFull() = scrollCategories(CompilationMode.Full())
    
        private fun scrollCategories(compilationMode: CompilationMode) =
            benchmarkRule.measureRepeated(
                ...
            ) {
                val categories =
                    device.wait(
                        Until.findObject(By.res("categories_lazy_column")),
                        1_000,
                    ) ?: error("카테고리 Lazy Column을 찾을 수 없음")
    
                repeat(2) {
                    categories.fling(Direction.DOWN)
                    categories.fling(Direction.UP)
                }
            }
    }

성능 측정을 위한 테스트 코드 작성은 끝났다! 이제 테스트를 실행해 성능 병목 지점을 분석해 보자.


성능 분석

테스트를 실행해 보면 아래와 같은 결과가 출력될 것이다.

Android Docs | Slow Rendering

To help ensure that a user's interaction with your app is smooth, your app must render frames in under 16ms to achieve 60 frames per second (fps).

이는 1초에 60프레임(60fps)을 유지하기 위한 기준이며, 이를 초과하면 화면이 버벅거리거나 애니메이션이 매끄럽지 않게 보일 수 있다.

실제 성능 측정 결과를 보면 스크롤 시 약 51~52개의 아이템이 렌더링 되며, 평균 93ms가 소요된다. 프레임 CPU 처리 시간은 대부분 16.6ms 이내로 유지되지만, P99 구간에서 최대 4.6ms가 초과해 간헐적으로 끊김이 발생할 가능성이 있다.

Item 단위 측정만으로는 성능 병목 지점을 정확히 파악하기 어려워, Item에 포함된 각 컴포저블의 렌더링 시간을 개별적으로 측정했다.

또한 Perfetto를 활용해 렌더링 시간을 더욱 정확하게 분석했다. Perfetto 측정을 위해 1편에서 소개한 방법으로 trace 파일을 추출한 뒤, SQL을 사용해 필요한 데이터를 추출했다. PerfettoSQL은 SQLite의 확장 버전으로, SQLite의 SQL 문법을 따른다.

SQL 문은 아래와 같이 작성했다.

SELECT
  name,
  AVG(dur) / 1e6 AS avg_duration_ms
FROM slice
WHERE name IN (
  'CategoryItem',
  'ThumbnailImage',
  ...
)
GROUP BY name
ORDER BY avg_duration_ms DESC;

분석 결과, 유독 시간이 많이 소요되는 부분은 CategoryFolder와 StaccatoCount였다.




유의미했던 성능 개선 시도

여러 가지 시도를 해봤지만, 이 포스트에서는 그중 성능 개선에 유의미한 영향을 준 시도만 다룰 예정이다.

Icon SVG를 XML이 아닌 Kotlin 파일로 변경

CategoryFolderStaccatoCount의 렌더링 시간이 오래 걸리는 원인을 찾기 위해 두 컴포넌트의 공통점을 생각해 봤다. 두 컴포넌트 모두 아이콘을 사용하고 있었으며, 이에 따라 아이콘 렌더링을 최적화할 방법을 찾아봤다.

변경 전(XML)

Icon(
    imageVector = ImageVector.vectorResource(id = R.drawable.icon_folder),
    ...
)

기존에는 아이콘 SVG를 XML로 변환한 리소스를 불러왔는데, XML을 ImageVector로 변환하는 과정에서 시간이 많이 소요된다고 판단했다. 그래서 SVG를 Kotlin 코드로 변환해 주는 사이트를 활용해 아이콘을 Kotlin 파일로 변경했다.

변경 후(Kotlin)

fun buildFolder() =
    ImageVector.Builder(
        name = "icon_folder",
        ...
    ).group {
        ...
    }.build()
object Icons {
    val Folder: ImageVector by lazy { buildFolder() }
    ...
}
Icon(
    imageVector = Icons.Folder,
    ...
)

아이콘을 변경한 뒤 성능을 측정해 본 결과, 아래와 같은 결과가 나타났다! Perfetto 분석 결과를 보면 CategoryFolder의 렌더링 시간이 변경 전보다 약 64.76% 개선된 것을 확인할 수 있었다.

CategoryFolderCategoryItem
변경 전0.30735427478753541.7245536628895184
변경 후0.108043761494252881.5853231120689655
결과약 0.199ms 감소 (약 64.76% 개선)약 0.139ms 감소 (약 8.06% 개선)

마찬가지로 스타카토 마커 개수 아이콘도 SVG를 XML에서 Kotlin 코드로 변경했더니, 성능이 개선된 것을 확인할 수 있었다.

StaccatoCountCategoryItem
변경 전0.33418880057803471.5863624710982658
변경 후0.107048083094555871.3669736246418338
결과약 0.22714ms 감소 (약 67.99% 개선)약 0.21939ms 감소 (약 13.83% 개선)

결과적으로 아이템에 포함된 모든 Icon을 XML에서 Kotlin 코드로 변경한 후 Item의 렌더링 시간이 약 20.73% 개선되었다.

CategoryItem
변경 전1.7245536628895184
변경 후1.3669736246418338
결과약 0.35758ms 감소 (약 20.73% 개선)

ConstraintLayout을 Row/Column + Slot 패턴으로 변경

처음에는 아이템을 RowColumn으로 구현했지만, 중첩이 심해 가독성이 떨어지고 유지보수가 어렵다고 판단해 ConstraintLayout으로 변경했다. ConstraintLayout을 사용하면서 코드 가독성은 개선됐지만, 내부적으로 제약 조건을 해석하고 계산하는 과정이 있어 상대적으로 성능 오버헤드가 발생할 가능성이 있다는 생각이 들었다.

이를 개선하기 위해 다시 Row와 Column을 사용하는 방향을 고민했지만, 동일한 방식으로 구현하면 가독성 문제가 다시 생길 수 있었다. 방법을 생각하던 중 예전에 레아가 강의해 주셨던 Jetpack Compose UI 조합하기 내용이 떠올랐다.

Compose UI 조합 방식으로 Slot 패턴Compound Component 패턴을 소개해 주셨다. 두 패턴은 성능 최적화 자체를 위한 패턴은 아니지만, 불필요한 중첩을 줄이고 UI 구조를 단순화할 방법이기 때문에 성능에도 간접적으로 긍정적인 영향을 줄 수 있겠다는 생각이 들었다.

두 패턴을 모두 시도해 본 결과, 현재 UI에서는 Compound Component보다 Slot 패턴이 더 적합해 최종적으로 Slot 패턴을 적용했다.

Compound Component 패턴은 UI 변화가 있어도 Slot을 계속 추가할 필요가 없어 컴포넌트 API가 복잡해지지 않는다는 장점이 있지만, 현재 UI는 구조상 여러 Slot을 활용할 수밖에 없어 해당 장점을 살리기 어려웠다.

이번 포스트는 성능 개선에 초점을 두고 있으므로 두 패턴 자체에 대해서는 자세히 다루지 않고, 코드 변경 전후를 중심으로 다룰 예정이다. 두 패턴에 대한 자세한 내용은 Suhyeon Kim Medium | Jetpack Compose UI 조합(Composition)하기 심화를 참고하길 바란다.

개선 전 - ConstraintLayout 사용

private const val THUMBNAIL_IMAGE_SIZE = 90

@Composable
fun CategoryItem(
    modifier: Modifier = Modifier,
    category: CategoryUiModel,
    onCategoryClicked: (Long) -> Unit,
) {
    val hasPeriod = category.startAt != null && category.endAt != null

    ConstraintLayout(
        modifier =
            modifier
                .padding(horizontal = 18.dp, vertical = 13.dp)
                .fillMaxWidth()
                .clickableWithoutRipple { onCategoryClicked(category.categoryId) },
    ) {
        val (
            thumbnail,
            color,
            period,
            title,
            participants,
            staccatoCount,
        ) = createRefs()

        DefaultAsyncImage(
            modifier =
                Modifier
                    .padding(end = 15.dp)
                    .size(THUMBNAIL_IMAGE_SIZE.dp)
                    .constrainAs(thumbnail) {
                        top.linkTo(parent.top)
                    },
            imageSizeDp = THUMBNAIL_IMAGE_SIZE.dp,
            url = category.categoryThumbnailUrl,
            placeHolder = R.drawable.default_image,
            errorImageRes = R.drawable.default_image,
            contentDescription = R.string.all_category_thumbnail_photo_description,
            radiusDp = 4.dp,
        )

        CategoryFolder(
            modifier =
                Modifier
                    .padding(end = 10.dp)
                    .constrainAs(color) {
                        start.linkTo(thumbnail.end)
                        top.linkTo(thumbnail.top)
                    }.padding(2.dp),
            color = category.color,
        )

        Text(
            modifier =
                Modifier.constrainAs(title) {
                    start.linkTo(color.end)
                    end.linkTo(parent.end)
                    width = Dimension.fillToConstraints
                    height = Dimension.preferredWrapContent

                    if (hasPeriod) {
                        top.linkTo(color.top)
                        bottom.linkTo(period.top)
                    } else {
                        top.linkTo(color.top)
                        bottom.linkTo(color.bottom)
                    }
                },
            text = category.categoryTitle,
            style = Title3,
        )

        CategoryPeriod(
            modifier =
                Modifier.constrainAs(period) {
                    start.linkTo(color.end)
                    top.linkTo(title.bottom, margin = 3.dp)
                    bottom.linkTo(color.bottom)
                    end.linkTo(parent.end)
                    width = Dimension.fillToConstraints
                },
            startAt = category.startAt,
            endAt = category.endAt,
        )

        if (category.isShared) {
            Participants(
                modifier =
                    Modifier
                        .padding(top = 22.dp)
                        .constrainAs(participants) {
                            if (hasPeriod) {
                                top.linkTo(period.bottom)
                            } else {
                                top.linkTo(title.bottom)
                            }
                            start.linkTo(thumbnail.end)
                            bottom.linkTo(parent.bottom)
                        },
                participants = category.participants,
                color = category.color,
            )
        }

        StaccatoCount(
            modifier =
                Modifier.constrainAs(staccatoCount) {
                    end.linkTo(parent.end)
                    bottom.linkTo(parent.bottom)
                },
            count = category.staccatoCount,
        )
    }
}

개선 후 - Row/Column + Slot 패턴 사용

@Composable
fun CategoryItem(
   modifier: Modifier = Modifier,
   category: CategoryUiModel,
   onCategoryClick: (Long) -> Unit,
   period: @Composable () -> Unit = {},
   participants: @Composable () -> Unit = {},
) {
   Row(
       modifier =
           modifier
               .padding(horizontal = 18.dp, vertical = 13.dp)
               .fillMaxWidth()
               .clickable { onCategoryClick(category.categoryId) },
       verticalAlignment = Alignment.CenterVertically,
   ) {
       DefaultAsyncImage(
           modifier =
               Modifier
                   .padding(end = 15.dp)
                   .size(90.dp),
           imageSizeDp = 90.dp,
           url = category.categoryThumbnailUrl,
           placeHolder = R.drawable.default_image,
           errorImageRes = R.drawable.default_image,
           contentDescription = R.string.all_category_thumbnail_photo_description,
           radiusDp = 4.dp,
       )

       Column {
           Row(
               verticalAlignment = Alignment.CenterVertically,
           ) {
               CategoryFolder(
                   modifier = Modifier.padding(top = 2.dp, end = 12.dp),
                   color = category.color,
               )

               Column {
                   Text(
                       modifier = Modifier,
                       text = category.categoryTitle,
                       style = Title3,
                   )
                   period()
               }
           }

           Spacer(modifier = Modifier.size(22.dp))

           Row(
               modifier = Modifier.fillMaxWidth(),
               verticalAlignment = Alignment.Bottom,
           ) {
               participants()
               Spacer(modifier = Modifier.weight(1f))
               StaccatoCount(count = category.staccatoCount)
           }
       }
   }
}

결과적으로 ConstraintLayout을 사용했을 때보다 Row와 Column을 사용했을 때, 성능이 약 16.21% 개선되었으며 Slot 패턴으로 코드 가독성도 함께 향상되었다.

CategoryItem
변경 전1.2901443646723647
변경 후1.0809766705882353
결과0.20917ms (약 16.21% 개선)

이 외에도 아래처럼 다양한 시도를 해봤지만, 성능적으로 눈에 띄는 변화는 없었다.

시도

  • 🟡 CategoryFolder Box의 배경 .background → .drawBehind
  • 🟡 참여자들 Lazy Row Item 감싸고 있는 Box 제거
  • 🟡 이미지 Size 동적(썸네일, 참여자 프로필)
  • 🟡 카테고리 아이템 Spacer 제거 후 Padding 적용
  • ❓참여자들 Lazy Row → Row (자세한 내용은 아래에서 다룰 예정)



결과

성능 개선에 가장 큰 영향을 미친 작업은 다음과 같다.

  • Icon SVG를 XML에서 Kotlin 파일로 변경
  • ConstraintLayoutRowColumn에 Slot 패턴을 적용하는 방식으로 변경

이 작업을 통해 frameOverrunMs 기준 전체 구간에서 평균 95% 이상의 성능 개선을 이뤘다.




성능 개선을 하면서 든 고민

Benchmark를 위한 CategoriesBenchmarkActivity의 위치

Benchmark 측정을 위해 CategoriesBenchmarkActivity를 별도로 생성했다. 그 이유는 다음과 같다.

  • 로그인 진입장벽: benchmark에서 토큰을 저장해 우회하려 했지만 실패
  • Lazy Column이 바텀시트 내부에 위치: 테스트를 위해 바텀시트를 Expanded 상태로 전환해야 하나, 상태 제어가 까다로움

테스트 대상 Activity는 앱 모듈에 있어야 하므로 CategoriesBenchmarkActivity를 앱 모듈에 위치시켰다. 다만, 실제 앱에서 사용되지 않는 Activity이기 때문에 이 위치가 적절한지는 고민이 된다. 더불어, CategoriesBenchmarkActivity 자체가 과연 필요한지도 다시 생각해 볼 여지가 있다.


참여자들 LazyRow -> Row 변경 여부

Android Codelab에 따르면 요소가 많지 않은 리스트의 경우 Row로 구현하는 것을 권장한다.

Android Codelab | Practical performance problem solving in Jetpack Compose

  • Lazy 레이아웃이 제한된 크기보다 훨씬 많은 item을 포함하는 레이아웃에서 우수
  • 문제는 Lazy 컴포지션이 필요하지 않을 때 불필요한 추가 비용을 발생시킨다는 점
    • Android Codelab에 따르면 불필요한 오버헤드를 위해 Row 사용 권장

다만, 실제로 성능을 측정해 본 결과 렌더링 시간이 오히려 더 길어졌고, 성능이 확실히 개선되었다는 근거를 찾지 못했다.

그리고 참여자 목록은 확장되거나 스크롤 될 가능성이 있어, 일단은 기존 방식을 유지하기로 했다. 이 부분에 대해서도 조금 더 알아봐야 할 것 같다.


Baseline Profile 도입 여부

Android Codelab | Practical performance problem solving in Jetpack Compose

By implementing Baseline Profiles, you can improve your app startup by 30% and reduce the code running in JIT mode at runtime by eight times as shown in the following image based on the Now in Android sample app:

안드로이드 코드랩에서는 성능 문제를 조사하기 전에 Baseline Profile을 먼저 생성할 것을 권장한다.

Baseline Profile을 적용하면 앱 실행 속도가 최대 30% 향상되고, 런타임에서 JIT 모드로 실행되는 코드도 최대 8배 줄일 수 있다고 한다.

하지만 스타카토는 규모가 크지 않아 Baseline Profile을 도입하는 것이 다소 과할 수 있다는 고민이 있다. 그래서 도입 여부는 팀원들과 충분히 논의해 결정할 계획이다.




Outro

위에서 언급한 고민과 Benchmark에 대해 아직 공부할 부분이 남아 있지만, 이번 성능 개선 결과에는 나름대로 만족하고 있다. 시간상으로 여유가 생기면 이러한 고민을 더 깊이 학습해 코드에 적용해 볼 생각이다.




참고 자료

profile
Android Developer

0개의 댓글