seoft

DiffUtil 아이템 개수와 데이터 교체 방법에 따른 종합 성능 테스트 본문

android

DiffUtil 아이템 개수와 데이터 교체 방법에 따른 종합 성능 테스트

seoft 2021. 10. 3. 22:55

개요

개인적으로 리사이클러뷰를 구현할 때 DiffUtil을 엄청 믿고 신뢰하며 사용해왔었는데, 팀 내에서 수동적인 notifyXXX 갱신 방식과 DiffUtil 방식의 차이에 대해 잠시 이야기가 나왔습니다.

notifyXXX 는 변경대상을 알고 대상 부분에만 변경요청을 하는데 비해, 디프유틸을 사용하게 되면 하나의 아이템에 대한 변경을 위해서 full size diff 를 검사가 필요하기 때문에 성능적인 측면에서 비효율적인 것은 사실입니다.

하지만 예전부터 디바이스 성능이 상향 표준화가 되면서 DiffUtil 도입시의 성능갭이 별로 차이가 나지 않는다면 구현 혹은 유지보수의 용이성과 티가 안나는 성능차이는 트레이드오프할 가치가 있을 것이고 그렇기에 구글 프레임워크에도 포함되어 나오고 있는 거라 생각하고 있었고,
그렇다면 어느 수준의 요구사항까지 DiffUtil의 성능이 커버가 될지, 리스트의 갯수 혹은 아이템 변경 방법에 따른 사용성에 어떤 영향을 미칠지 궁금하여 테스트를 진행해봤습니다.

 

 

구글에서 설명하는 DiffUtil

구글 디벨롭문서에서 DiffUtil 관련하여 기재된 사항중 성능과 테스트 관련하여 작성해보겠습니다.
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

DiffUtil 내부에서 Myers Difference 알고리즘을 사용하며 성능은 다음과 같습니다.

변경점을 찾는 비용
공간복잡도 : O(N) 
시간복잡도 : O(N + D^2) 

M의 추가항목과 N의 제거항목을 찾게 될 : O(MN)의 추가 시간복잡도

또한 다음과 같은 성능 지표를 안내하고 있습니다.

테스트 환경

 - 디바이스 Nexus5 (OS M)
 - random UUID (36 사이즈의 스트링?) 아이템


100 items and 10 modifications: avg: 0.39 ms, median: 0.35 ms
100 items and 100 modifications: 3.82 ms, median: 3.75 ms
100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms
1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms
1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms
1000 items and 200 modifications: 27.07 ms, median: 26.92 ms
1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms

 

 

테스트 앱 구성

구글에서 제공하는 대략적인 성능 평가가 있었지만 저는 다음 두 사유로 직접 테스트를 진행하게 되었습니다.

 - Nexus5 기기가 13년도의 오래된 단말기라 현시점의 비교적 성능이 업된 단말기에서의 결과 도출하기 위함
 - 디벨롭 문서에 기재된 수정 케이스 뿐만 아니라 추가, 셔플, 삭제 등의 더 다양한 케이스에서의 결과 도출하기 위함

 

테스트를위해 앱을 구현하였고 테스트앱은 여기서 만나보실 수 있습니다 

 

-> https://github.com/seoft/android-laboratory/tree/dev/diff-util-test

 

GitHub - seoft/android-laboratory

Contribute to seoft/android-laboratory development by creating an account on GitHub.

github.com

 

테스트앱 스크린샷

 

 

테스트 환경

 - 현 시점에 적당히 마지노선의 단말기(?)인 갤럭시 S8(OS 9)로 테스트
 - 기존 테스트의 UUID 보다는 실제 유즈케이스와 더 밀접한 환경(?)
 - 멀티타입의 뷰홀더
 - 실제 모델 id값으로 areItemsTheSame을, ui 변경대상 파라미터만으로 areContentsTheSame 로 비교
 - payload 전달로 뷰 부분 업데이트 진행
 - 테스트 케이스 : 순차적 추가, 랜덤 추가, 삭제, 셔플, 수정, (단일 추가,수정,삭제)
 - 테스트 개수 범위 : 200*n (~1000) , 5000*n (~50000)
 - 단일 업데이트 경우 인덱스 마지막 부근에서 진행

* 부분 삭제, 변경 경우 다음 테스트 갯수(last 25000)에서 다음 테스트갯수 - 현 테스트 갯수만큼 진행,(ex, 2번째 테스트 경우 600개에서 200개 수행)

 

 

테스트 결과

테스트 결과는 다음과 같습니다. 각 항목을 10번 진행하여 평균낸 결과이며, 20초가까이 넘어가는 내용들은 무의미하다고 판단하여 복수 테스트는 하지않았습니다.

*평균내기 전 raw 값도 포함된 excel 파일은 github 테스트앱 프로젝트 디렉토리에 포함되있습니다.

 

 

결론

테스트 결과를 보고 몇 가지 기술하자면,


- 전체 셔플에 대한 비교수행 비용은 굉장히 큽니다.(충분히 나올 수 있는 요구사항이고, 셔플범위가 넓을 경우에는 삭제 후 새로 추가하는게 더 용이해보입니다)
- 리사이클러뷰가 디폴트로 애니메이션 가지게 되는데 이를 off 할 경우 리스트 갯수와 추가타입에 따라 성능차이가 있을 수 있습니다.
- 개인차가 있어 몇 ms부터 느리다고 판단하기는 힘들지만 랜덤 추가방식 경우 2000개, 삭제 경우 6000개 이상이 될 경우 160ms, 200ms 가 넘으면서 사용자 입장에서 반응이 느리게 느껴질 수도 있을듯합니다.
- 제가 주로 리스트를 구성할 때에는 페이징과 부분 아이템의 일부의 상태를 교체하는 요구사항이 일반적인 것 같습니다. 이러한 요구사항에서는 아이템의 갯수가 많아져도 크게 무리가 되지 않아 보입니다. (이 부분은 특정 리스트 기능을 사용할때 유저의 max 페이징 수 지표가 파악되면 더 좋을듯하네요..)

조심스럽게..

 

" 일반적인 요구사항에서는 DiffUtil을 도입해도 충분히 괜찮으나, 리스트 구성이나 사용방식에 대해 특이사항이 있을 경우 성능 검토 고려가 필요하다 "

 

로 개인적으로 결론지어봅니다.

 

 

위에 테스트 프로젝트 구성 외에도 디바이스 환경이나 실제 구성에서의 diff 조건, 뷰홀더명세 등등 테스트 결과에 영향이 미치는 요소가 더 있기 때문에, 제가 가정한 조건에 따른 결과임을 염두 부탁드리며, 추가로 필요시 코드 수정해서 테스트 하셨으면 하는 바램으로 공유드립니다.


읽어주셔서 감사합니다.

Comments