다크다크는 메달한 상황을 설명하는 사용자 용어로, 모바일 앱이 늘리고 고작스럽게 작동하는 경우를 말합니다: 가끔은 정상으로 반응하다가 갑자기 몇 초 동안 먹혀 버립니다. 기술적 문맥에서 “다크다크다”는 재발하는 GC 팼즈, 동기 작업에 의한 메인 스레드 차단, 비최적인 데이터 구조에 의해 발생하는 랜과 마이크로 프리즈의 조합을 의미합니다. Android Performance Benchmarking Guide에 따르면, 응답 시간을 300ms에서 100ms로 줄이면 사용자 유지율이 25% 증가합니다. 다크다크하는 현상을 진단하려면 CPU 및 메모리 프로파일링과 가비지 수집 빈도 분석이 필요합니다.
중요한 점
다크다크는 사용자가 주관적으로 느린 앱 성능을 설명하는 비정식 용어입니다. 일정한 지연으로 나타나는 랜과 달리, 다크다크는 비정규적인 먹큼으로 구성됩니다: 앱이 몇 초 동안 완벽하게 작동하다가 갑자기 1–3초 동안 “고민하는” 것같이 보일 수 있습니다.
프로파일링 관점에서 다크다크는 최대 지연이 100ms를 초과하는 일루된 프레임 녹화(jank)의 연속으로 나타난니다. FPS 그래프에서는 급격한 하락으로 보입니다: 60 → 20 → 55 → 10 FPS. FPS가 일정하게 낮은 랜과 달리, 다크다크는 트렌 있는 변동성을 갖습니다.
앱이 다크다크할 때, 사용자는 늘어지는 이유를 이해할 수 없습니다: 화면이 부드럽게 스크롤되다가 갑자기 일이상 멈추는 경우가 있습니다. 이는 직절감을 유발하고 앱에 대한 신뢰를 떨어뜨립니다. Google에 따르면, 로딩에 3초 이상 걸리면 53% 사용자가 사이트나 앱을 떠낝니다.
다크다크의 간헉적인 성질은 문제가 지속적인 과부하를 때문이 아니라 이벤트 구동 요인에 의해 발생한다는 것을 나타냅니다. 일반적인 시나리오를 더북시다.
Android의 ART 런타임에서는 가비지 수집이 모든 앱 스레드를 멈춰서킵니다. 코드가 많은 일시 객체를 만드는 경우, 예를 들어 onBindViewHolder가 호출될 때마다 연결을 통해 새 String을 만드는 경우, GC가 더 잠발발생합니다. 팼즈는 헥 크기와 객체 생성 대에 따라 5–50ms 지속된 수 있습니다. 사용자는 이것을 갑자기의 “고민”으로 인식합니다.
Android의 Room과 iOS의 Core Data는 비동기 쿼리를 지원하지만, 개발자들은 간편성을 위해 getValue()를 호출하거나 runBlocking을 통해 쿼리를 실행하는 경우가 많습니다. 10,000행 테이블에서의 조인이 있는 무거운 SELECT는 200–500ms가 걸릴 수 있으며, 그 동안 UI가 완전히 차단됩니다.
카메라 이미지(12MP, 4000x3000 px)를 크기 조정 없이 로드하면 Bitmap 디코딩에 200ms까지 걸릴 수 있습니다. 이미지를 비동기적으로 로드하지만 제한된 스레드 풀이 없는 경우, 5–6개의 디코드를 동시에 실행하면 CPU가 과부하되어 이동하는 늘릴이 발생할 수 있습니다.
간헉적인 늘맴을 진단하는 것은 일정한 랜을 진단하는 것보다 더 어렵습니다. 매 실행마다 문제가 재현되지 않을 수 있기 때문입니다. 장기간에 걸쳐 통계 데이터를 수집하는 것이 필요합니다.
Android Studio Memory Profiler는 메모리 사용러만 가아니라 GC 이벤트도 표시합니다: 빈도, 유형(Concurrent, Full), 기간. 유지 상태에서 GC가 5초마다 1번 이상 발생하면 과잉 할당의 증거입니다. 다크다크하는 순간에 헥 덤프를 찍으면 어떤 객체가 메모리를 차지하고 있는지 알 수 있습니다.
iOS에서는 Instruments의 Allocations 템플릿을 사용하여 객체 생성과 해제를 추적하십시오. Generations을 사용하면 동작 사이에 헥 스냅샷을 찍고 메모리에 남아있는 객체를 확인할 수 있습니다. 해제되지 않는 지속 객체는 메모리 축적과 이후 팼즈의 원인입니다.
JankStats는 실시간으로 놀친 프레임 메트릭스를 수집하는 Android 라이브러리입니다. 각 jank를 현재 시나리오( 예: “리스트 스크롤”, “화면 열기”)와 연결하여 어떤 특정 작업이 다크다크를 유발하는지 파악할 수 있습니다.
Android에서 먹큼 추적을 위한 JankStats 정합 예시:
class MainActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.create(this.window.decorView) { frameData ->
if (frameData.isJank()) {
Log.w("Jank", "Duration=${frameData.durationMs}ms")
}
}
}
}
다크다크를 해결하려면 각 원인별로 대상적인 작업이 필요합니다. 만능의 해결책은 없습니다 — 특정 성능 프로파일의 분석이 필요합니다.
리스트에 1000+ 아이템이 있고 모두 한번에 로드되면 그것은 확실한 다크다크입니다. Android의 Paging 3와 iOS의 NSFetchedResultsController는 사용자가 스크롤할 때 데이터를 나누어 로드합니다. 사용자는 첫 10–20개의 아이템만 보고 나머지는 백그라운드에서 로드됩니다.
Room은 Android Studio의 Inspection Tool을 통해 쿼리 프로파일링을 허용합니다: 실행 시간, 반환된 행 수, 쿼리 계획이 표시됩니다. WHERE와 ORDER BY 열에 인덱스를 추가하면 쿼리 시간을 300ms에서 5ms로 줄일 수 있습니다. iOS에서는 Instruments의 Core Data Profiler가 비슷한 확인을 수행합니다.
백그라운드 동기화, 파일 다운로드, 데이터 처리 — 이모두 WorkManager(Android) 또는 Background Tasks(iOS)를 통해 실행해야 합니다. 동기화가 UI 스레드에서 실행되면 앱이 작업 중에 다크다크합니다. WorkManager는 배터리 및 네트워크 상태를 고려하여 백그라운드 스레드에서의 실행을 보장합니다.
Android에서 WorkManager를 통한 백그라운드 동기화 예시:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("동기화", "백그라운드 스레드에서 데이터 동기화 중")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
효율적인 메모리 및 스레드 관리 원칙을 따른다면 코딩 단계에서 다크다크를 방지할 수 있습니다.
Baseline Profiles는 Android가 JIT 대신 사전에 (AOT) 컴파일하는 클래스와 메소드의 목록입니다. 프로파일이 없으면 매 새 화면이 첫 번째 열릴 때 컴파일되어 100–500ms 지연이 발생합니다. 주요 화면에 대한 Baseline Profile을 작성하고 baseline-profile-gradle-plugin을 통해 Gradle에서 생성을 사용하십시오.
푛 경로는 모든 프레임에서 실행되는 코드입니다: onBindViewHolder, draw, layoutSubviews. 이 메소드에서 객체를 생성하는 것을 피하십시오: 객체 풀 사용, 연결 대신 StringBuilder 사용, 포맷된 문자열과 포맷터 캐시. 모든 추가 할당은 다음 GC를 가까워집니다.
CI 파이프라인에 리스트 스크롤 및 화면 열기 시나리오가 있는 Macrobenchmark를 추가하십시오. 기준치를 설정합니다: 프레임 시간의 99번째 백분위수는 16ms를 초과해서는 안 됩니다. 기준치를 초과하면 최적화가 완료될 때까지 빌드가 거부됩니다.
자주 묻는 질문
랜은 일정한 지연입니다(예: 탭마다 200ms). 다크다크는 간헉적입니다: 앱이 정상으로 작동하다가 갑자기 1–3초 늘어지다가 다시 정상으로 돌아옵니다. 원인은 GC 팼즈나 데이터베이스 동기 쿼리같은 이벤트 구동 요인입니다.
Android Studio에서 Memory Profiler를 사용하십시오: Memory 탭에서 기간별 GC 이벤트를 보여줍니다. 프로덕션 모니터링을 위해서는 커스텀 트레이스가 있는 Firebase Performance Monitoring을 적분하십시오. iOS에서는 Malloc Debug를 사용하고 Instruments에서 할당 대를 표시하십시오.
간접적으로 — 그러합니다. 서버 응답이 지연되고 UI가 동기적으로 기다리는 경우, 앱이 먹힉니다. 요청이 비동기적이라도 응답 처리가 UI 스레드에서 이루어지면 다크다크가 발생합니다. 해결 방법은 코루틴과 진행 표시기를 사용한 비동기 처리입니다.
올바르게 사용하지 않으면 KMP가 상호 운용성을 위해 과잉 래퍼 객체를 생성할 수 있습니다. iOS에서는 이로 인해 할당 빈도가 증가하고 결국 ARC 팼즈가 많아집니다. @ObjCName을 사용하고, expect/actual을 최적화하고, UI 푛 경로에서 공유 코드로의 잠반 호출을 피하십시오.
android:largeHeap=”true”를 통한 헥 확장은 GC를 지연시키지만 할당의 원인을 없애지는 못합니다. GC가 마지막으로 실행될 때, 더 많은 객체를 순회해야 하기 때문에 팼즈가 더 길어집니다. 해결 방법은 헥을 확장하는 것이 아니라 할당 수를 줄이는 것입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.