Flutter 튜토리얼 52편: 렌더링 성능 프로파일링

Flutter 렌더링 성능 프로파일링#

Flutter 앱이 부드럽게 동작하려면 초당 60프레임(60fps)을 유지해야 합니다. 이 튜토리얼에서는 렌더링 성능을 측정하고 최적화하는 방법을 배웁니다.

학습 목표#

  • 60fps의 의미와 중요성 이해하기
  • Performance Overlay 활용하기
  • DevTools Performance View로 병목 분석하기
  • 일반적인 성능 문제 해결하기

1. 60fps와 프레임 예산#

Why: 왜 60fps가 중요한가요?#

인간의 눈은 초당 60프레임 이상에서 움직임을 자연스럽게 인식합니다. 프레임 속도가 떨어지면 사용자는 앱이 “버벅거린다”고 느끼게 됩니다. 이는 사용자 경험을 크게 저하시킵니다.

What: 프레임 예산의 개념#

60fps를 달성하려면 각 프레임을 16밀리초(ms) 안에 완성해야 합니다.

flowchart LR A[1초 = 1000ms] --> B[60 프레임] B --> C[1000ms ÷ 60] C --> D["≈ 16ms/프레임"]
프레임 속도프레임당 시간사용자 경험
60fps~16ms매우 부드러움
30fps~33ms약간 버벅임
15fps~66ms심하게 버벅임

What: UI 스레드와 Raster 스레드#

Flutter는 두 개의 주요 스레드1를 사용합니다:

flowchart TD A[Flutter 렌더링] --> B[UI 스레드] A --> C[Raster 스레드] B --> B1[위젯 빌드] B --> B2[레이아웃 계산] B --> B3[페인트 명령 생성] C --> C1[페인트 명령 실행] C --> C2[GPU로 전송] C --> C3[화면에 표시]
스레드역할병목 시 증상
UI 스레드위젯 빌드, 레이아웃애니메이션 끊김
Raster 스레드화면 렌더링화면 깜빡임

How: 프로파일 모드 사용#

성능을 정확히 측정하려면 프로파일 모드2로 실행해야 합니다.

Terminal window
# 명령줄에서 프로파일 모드 실행
flutter run --profile

VS Code에서는 launch.json에 다음 설정을 추가합니다:

{
"configurations": [
{
"name": "Flutter (Profile)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
}
]
}

Watch out: 주의사항#

디버그 모드에서는 성능이 정확하지 않습니다. JIT 컴파일과 디버그 검사로 인해 실제보다 느리게 동작합니다. 항상 프로파일 모드나 릴리스 모드에서 성능을 측정하세요.


2. Performance Overlay 사용하기#

Why: Performance Overlay가 필요한 이유#

Performance Overlay3는 앱 실행 중 실시간으로 성능을 확인할 수 있는 도구입니다. 코드를 수정하지 않고도 바로 성능 문제를 발견할 수 있습니다.

What: 그래프 읽는 방법#

Performance Overlay는 두 개의 그래프를 표시합니다:

flowchart TD A[Performance Overlay] --> B[상단 그래프] A --> C[하단 그래프] B --> B1[Raster 스레드] B --> B2[GPU 작업 시간] C --> C1[UI 스레드] C --> C2[Dart 코드 실행 시간]
그래프 색상의미
녹색 막대16ms 이하 (정상)
빨간색 막대16ms 초과 (문제)
빨간색 점선16ms 기준선

How: Performance Overlay 활성화#

코드로 활성화#

import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
showPerformanceOverlay: true, // 여기에 추가
home: const MyHomePage(),
),
);
}

DevTools에서 활성화#

  1. flutter run --profile로 앱을 실행합니다
  2. DevTools를 열고 Performance 탭으로 이동합니다
  3. “Performance Overlay” 버튼을 클릭합니다

How: 문제 진단하기#

// 나쁜 예: UI 스레드에서 무거운 작업
class BadExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 이 작업이 16ms를 초과하면 프레임 드롭 발생
final result = expensiveCalculation();
return Text(result);
}
}
// 좋은 예: 미리 계산하거나 비동기로 처리
class GoodExample extends StatefulWidget {
@override
State<GoodExample> createState() => _GoodExampleState();
}
class _GoodExampleState extends State<GoodExample> {
String? _result;
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
final result = await compute(expensiveCalculation, null);
setState(() => _result = result);
}
@override
Widget build(BuildContext context) {
return _result != null
? Text(_result!)
: const CircularProgressIndicator();
}
}

Watch out: 주의사항#

Performance Overlay는 프로파일 모드에서만 정확합니다. 또한 오버레이 자체도 약간의 성능 오버헤드가 있으므로, 최종 측정은 오버레이 없이 진행하는 것이 좋습니다.


3. DevTools Performance View#

Why: 더 상세한 분석이 필요한 이유#

Performance Overlay는 문제가 있는지 여부만 알려줍니다. 정확히 어떤 코드가 문제인지 알려면 DevTools의 Performance View가 필요합니다.

What: Performance View의 구성#

flowchart TD A[Performance View] --> B[Frame Chart] A --> C[Timeline Events] A --> D[CPU Profiler] B --> B1[프레임별 처리 시간] C --> C1[상세 이벤트 타임라인] D --> D1[함수별 CPU 사용량]

How: Performance View 사용하기#

  1. 프로파일 모드로 앱을 실행합니다
  2. DevTools를 엽니다 (터미널에 표시된 URL 클릭)
  3. Performance 탭을 선택합니다
  4. “Record” 버튼을 클릭하여 녹화를 시작합니다
  5. 앱에서 성능 문제가 발생하는 동작을 수행합니다
  6. “Stop” 버튼을 클릭합니다

How: Frame Chart 분석#

Frame Chart에서 각 프레임의 처리 시간을 확인할 수 있습니다:

색상의미조치
파란색UI 스레드 작업위젯 빌드 최적화 필요
초록색Raster 스레드 작업렌더링 최적화 필요
빨간색 프레임16ms 초과즉시 조사 필요

How: Timeline Events 분석#

특정 프레임을 클릭하면 상세 타임라인을 볼 수 있습니다:

// 타임라인에 커스텀 이벤트 추가하기
import 'dart:developer';
void myFunction() {
Timeline.startSync('myFunction');
try {
// 측정하고 싶은 코드
doSomething();
} finally {
Timeline.finishSync();
}
}

Watch out: 주의사항#

DevTools는 많은 데이터를 수집하므로, 너무 오래 녹화하면 분석이 어려워집니다. 문제가 발생하는 구간만 짧게 녹화하는 것이 좋습니다.


4. 일반적인 렌더링 문제#

Why: 자주 발생하는 문제를 알아야 하는 이유#

대부분의 성능 문제는 몇 가지 패턴으로 귀결됩니다. 이러한 패턴을 알고 있으면 빠르게 문제를 해결할 수 있습니다.

What: saveLayer의 과도한 사용#

saveLayer4는 비용이 큰 연산입니다. 일부 위젯은 내부적으로 saveLayer를 호출합니다.

flowchart TD A[saveLayer 호출 위젯] --> B[Opacity] A --> C[ShaderMask] A --> D[ColorFilter] A --> E[Clip 위젯들]

How: Opacity 최적화#

// 나쁜 예: Opacity 위젯 사용
Opacity(
opacity: 0.5,
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
)
// 좋은 예: 색상에 직접 투명도 적용
Container(
color: Colors.red.withOpacity(0.5),
width: 100,
height: 100,
)

What: 불필요한 위젯 리빌드#

위젯이 너무 자주 리빌드되면 성능이 저하됩니다.

// 나쁜 예: 전체 위젯 리빌드
class BadCounter extends StatefulWidget {
@override
State<BadCounter> createState() => _BadCounterState();
}
class _BadCounterState extends State<BadCounter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 이 비싼 위젯도 매번 리빌드됨
const ExpensiveWidget(),
Text('Count: $count'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('Increment'),
),
],
);
}
}
// 좋은 예: 필요한 부분만 리빌드
class GoodCounter extends StatefulWidget {
@override
State<GoodCounter> createState() => _GoodCounterState();
}
class _GoodCounterState extends State<GoodCounter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// const로 선언하여 리빌드 방지
const ExpensiveWidget(),
// 또는 별도 위젯으로 분리
CounterText(count: count),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('Increment'),
),
],
);
}
}

How: RepaintBoundary 사용#

RepaintBoundary5로 리페인트 영역을 제한할 수 있습니다.

class OptimizedList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return RepaintBoundary(
child: ComplexListItem(index: index),
);
},
);
}
}

What: 캐시되지 않는 이미지#

동일한 이미지를 반복해서 디코딩하면 성능이 저하됩니다.

// 이미지 캐싱 확인
class ImageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Image.asset(
'assets/image.png',
cacheWidth: 200, // 캐시 크기 지정
cacheHeight: 200,
);
}
}

Watch out: 주의사항#

RepaintBoundary를 과도하게 사용하면 오히려 메모리가 증가합니다. 성능 문제가 확인된 곳에만 선택적으로 사용하세요.


5. 위젯 리빌드 프로파일러#

Why: 리빌드를 추적해야 하는 이유#

불필요한 위젯 리빌드는 성능 저하의 주요 원인입니다. 어떤 위젯이 언제 리빌드되는지 추적하면 최적화 포인트를 찾을 수 있습니다.

What: Widget Rebuild Stats#

DevTools의 Performance 탭에서 “Track Widget Builds”를 활성화하면 위젯별 빌드 횟수를 확인할 수 있습니다.

flowchart TD A[Widget Rebuild Stats] --> B[빌드 횟수] A --> C[빌드 시간] A --> D[리빌드 원인] B --> B1[위젯별 총 빌드 횟수] C --> C1[각 빌드에 걸린 시간] D --> D1[setState, 상위 위젯 변경 등]

How: 리빌드 추적 활성화#

// main.dart에서 디버그 플래그 활성화
import 'package:flutter/rendering.dart';
void main() {
// 리빌드 추적 (프로파일/디버그 모드에서만)
debugProfileBuildsEnabled = true;
runApp(const MyApp());
}

How: 불필요한 리빌드 방지#

// 1. const 생성자 활용
const MyWidget(); // 리빌드 방지
// 2. shouldRebuild 구현 (InheritedWidget)
class MyInheritedWidget extends InheritedWidget {
final int data;
const MyInheritedWidget({
required this.data,
required super.child,
});
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return data != oldWidget.data; // 데이터가 변경된 경우만 알림
}
}
// 3. 선택적 리빌드 (Consumer, Selector)
// Provider 패키지 사용 예시
Consumer<CounterModel>(
builder: (context, counter, child) {
return Text('${counter.count}');
},
)

Watch out: 주의사항#

debugProfileBuildsEnabled는 성능에 영향을 주므로 배포 빌드에서는 반드시 제거해야 합니다.


6. 성능 최적화 체크리스트#

측정 및 분석#

  • 프로파일 모드로 실행
  • Performance Overlay로 프레임 드롭 확인
  • DevTools Performance View로 상세 분석

일반적인 최적화#

  • Opacity 대신 색상 투명도 사용
  • const 생성자 활용
  • 무거운 작업은 Isolate로 분리
  • ListView.builder 사용 (긴 리스트)

고급 최적화#

  • RepaintBoundary 적절히 사용
  • saveLayer 호출 최소화
  • 이미지 캐싱 설정
  • 위젯 리빌드 최적화

마무리#

이번 튜토리얼에서는 Flutter 앱의 렌더링 성능을 측정하고 최적화하는 방법을 배웠습니다.

핵심 정리#

주제핵심 내용
프레임 예산16ms 이내에 프레임 완성 (60fps)
스레드 구조UI 스레드 + Raster 스레드
측정 도구Performance Overlay, DevTools
주요 문제saveLayer, 불필요한 리빌드, 캐시 미사용

다음 단계#


참고 자료#

Footnotes#

  1. 스레드(Thread)는 프로그램 내에서 독립적으로 실행되는 작업 흐름입니다.

  2. 프로파일 모드(Profile Mode)는 성능 측정을 위한 빌드 모드로, 릴리스와 비슷한 성능을 보이면서 디버깅 도구를 사용할 수 있습니다.

  3. Performance Overlay는 앱 화면 위에 성능 그래프를 표시하는 디버깅 도구입니다.

  4. saveLayer는 새로운 그래픽 레이어를 생성하는 연산으로, 투명도나 효과 적용 시 사용됩니다.

  5. RepaintBoundary는 자식 위젯을 별도의 레이어로 분리하여 불필요한 리페인트를 방지합니다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 52편: 렌더링 성능 프로파일링
https://moodturnpost.net/posts/flutter/flutter-performance-rendering/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차