[Compose] Composable 함수의 계층이 깊어지면 성능 이슈가 발생할까?

안녕하세요. 오늘은 개발을 하다가 문득 든 생각을 주제로 글을 써보려합니다.

‘Composable 함수의 계층이 깊어지면 어떤 손해가 발생할까?’

선언형 UI인 Compose를 이용해 개발을 진행하다보면, 함수의 중첩이 많이 발생하게 됩니다. UI를 잘게 쪼개고 묶은 과정의 연속이라고도 할 수 있죠. 디자인 의도를 만족하려다보면, Row, Column, Box를 계속해서 포개게 됩니다. Layout Inspector를 통해 계층을 확인할 수 있는데, 꽤 깊은 depth를 보면, ‘이거 이대로 개발해도 성능에 문제가 없을까?’라는 의문이 들게 됩니다.

오늘 회사에서 다른 분들과 이야기를 하다가 이 부분에 대해 이야기가 나오게 되었습니다. 기존 XML 개발 방식에서는 Constraint Layout을 주로 사용하며, 계층 없이 구현하는 것을 기본으로 하는데, Compose와 같이 계층을 깊게 가져가도 성능 상 문제가 없을까 하는 것이 그 이야기의 주제였지요.

아쉽게도 명확하게 답을 내린 사람은 없었습니다. 다만 구글 가이드에 따라 ‘문제 없을 것이다’ 라고 판단할 수 밖에 없었죠.

Compose의 레이아웃 목표

가이드라인을 보면, 성능에 영향을 미치지 않고 원하는 만큼 깊이 중첩할 수 있다고 되어있습니다.

그래서 여러번 중첩하는 것과, 중첩하지 않은 것에 성능이 얼마나 차이가 있는지를 확인하기 위해 테스트를 해보았습니다.

우선 테스트에 적용한 레이아웃은 아래와 같습니다. 왼쪽은 중첩되지 않은 구조로 구성한 것이고, 오른쪽은 중첩된 구조로 구성한 것입니다. 구조를 눈으로 확인하기 위해 모든 컬럼에는 1.dp 의 Padding 값을 부여하였습니다.

1. 중첩되지 않은 구조
2. 중첩된 구조

간략한 코드를 나타내면 아래와 같습니다.

Column() {
  Column() {
    Button() {Text(1)}
  }
  Column() {
    Button() {Text(2)}
  }
  Column() {
    Button() {Text(3)}
  }
}
Column() {
  Column() {
      Column() {
          Column() {
            Button() {Text(1)}
          }
          Button() {Text(2)}
      }
      Button() {Text(3)}
    }
  }
}

코드 상으로 보았을 때 가장 바깥을 감싸는 Column을 포함하여 4개, Text Button은 3개로 구성 요소의 수는 동일한 코드입니다. 다만, 계층에는 차이가 있어, 중첩되지 않은 구조에서는 가장 깊은 depth의 아이템은 Column > Column > Button(Surface) > Text 가 될 것이고, 중첩된 구조에서는 Colum > Column > Column > Column > Button(Surface) > Text가 될 것입니다.

위의 코드는 중첩된 구조라 하더라도 그 깊이가 깊지 않기 때문에 유의미한 결과를 얻을 수 없을 것 같습니다. 그렇기에, for문과 재귀함수를 이용하여, 많은 수의 Column Composable을 생성하도록 코드를 구성하고, 이를 테스트 해보았습니다.

테스트에 사용한 코드는 아래와 같습니다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Test1Theme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    val time = System.currentTimeMillis()
                    val iteration = 10
                    Column(
                        Modifier.verticalScroll(rememberScrollState())
                    ) {
                        //Test(iteration, time) //중첩되지 않은 구성
                        RecTest(iteration, time) //중첩된 구성
                    }
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    Test1Theme {
        Greeting("Android")
    }
}

@Composable
fun Test(i: Int, time: Long) {
    for (k: Int in i downTo 1) {
        Column(Modifier.padding(1.dp)) {
            Button({}) { Text("$k") }
        }
        if (k == 1) Log.d("test", "${System.currentTimeMillis() - time}")
    }
}

@Composable
fun RecTest(i: Int, time: Long) {
    if (i == 0) {
        Log.d("test", "${System.currentTimeMillis() - time}")
        return
    } else {
        Column(Modifier.padding(1.dp)) {
            Button({}) { Text("$i") }
            RecTest(i - 1, time)
        }
    }
}

Composable 함수가 생성되기 전 시간과 모든 Composable 함수를 그리고 난 뒤의 시간을 측정하여 어느 정도 소요되었는 지를 확인합니다. 구성되는 Composable 함수의 의 수는 코드 내 iteration 변수입니다. 아래 표는 각각의 테스트에서 측정된 소요시간입니다.

가장 마지막에 그려지는 Composable을 기준으로 시간 로그를 취득하여 소요시간의 근사치를 얻었습니다. (ms 단위)

iteration중첩되지 않은 구성중첩된 구성
18079
10131133
100325536
10009991909
200013705458
중첩되지 않은 구성과 중첩된 구성의 Composition 소요시간 차이

위의 표로부터, 같은 개수의 함수로 구성하더라도, 중첩되는지, 중첩되지 않는지에 따라 성능이 다르다는 것을 확인할 수 있습니다. 극단적인 케이스로, 2000개의 Column Button Text를 중첩하지 않고 구성한 것과, 중첩하여 구성한 것에 4초 가량의 Composition 차이가 발생한다는 것을 확인할 수 있습니다.

다만, 우리는 UI의 성능을 향상시킴과 동시에 빠르게 개발 대응을 해야 할 필요가 있습니다. 화면을 구성할 때 개발 효율적으로 구성해야 한다는 의미이지요. 사실 실제 개발 케이스에서는 디자인을 만족 시키기 위해 수십 개의 계층 정도 선에서 해결되지 않을까 싶습니다. ( 실제 제가 회사에서 개발하고 있는 케이스의 경우 적어도 100개의 depth를 넘지는 않는 것 같습니다. ) 그렇다면 어지간해서는 계층의 깊이로 인해 성능 loss 가 발생할 우려는 하지 않아도 된다고 볼 수 있을 것 같습니다.

혹시 잘못된 부분이나 의견 있으시면 댓글로 언제든지 말씀 부탁드립니다 🙂

세상 모든 안드로이드 개발자 분들 파이팅!

결론 : Composable 함수가 많이 중첩될 경우 성능 loss가 발생할 수 있으나, 실전 케이스에서 유의미한 성능 loss가 발생한다고 보기는 어렵다.


게시됨

카테고리

작성자

댓글

“[Compose] Composable 함수의 계층이 깊어지면 성능 이슈가 발생할까?” 에 하나의 답글

  1. 재찌 아바타
    재찌

    후니 짱👋

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다