모바일 앱의 FPS: 본질, 계산 및 최적화

저자: IT Sectr 게시일: 2026-04-01 읽는 시간: 11 분

FPS(Frames Per Second)는 그래픽 시스템이 1초에 렌더링하는 개별 프레임 수를 나타내는 지표입니다. 모바일 개발에서 FPS는 UI 성능의 표준 지표이며, FPS가 높을수록 애니메이션이 더 부드럽고 인터페이스 반응성이 향상됩니다. Google Android Performance, 2025에 따르면, 모바일 앱의 목표 FPS는 초당 60프레임으로, 인간의 눈이 움직임을 연속적이고 부드럽게 인식하는 임계값입니다.

핵심 포인트

  • FPS — 초당 프레임 수, UI 부드러움의 주요 지표입니다.
  • 목표값 — 60 FPS, 프레임 시간 — 16.6ms입니다.
  • 고주사율 디스플레이의 경우 120 FPS가 필요합니다(프레임당 8.3ms).
  • 30 미만으로 FPS가 떨어지면 육안으로 끊김과 지연이 감지됩니다.
  • 프로덕션에서 FPS를 모니터링하면 성능 회귀를 식별하는 데 도움이 됩니다.

FPS란

FPS(Frames Per Second)는 컴퓨터 그래픽, 비디오 및 모바일 인터페이스에서 사용되는 프레임률 측정 단위입니다. 각 프레임은 짧은 시간 동안 화면에 표시되는 정적 이미지입니다. 빠른 프레임 변화와 함께 뇌는 이를 연속적인 움직임으로 인식합니다. 이 효과를 시각 지속성이라고 합니다. 모바일 앱의 경우 FPS는 중요한 지표입니다. 프레임이 하나라도 누락되면 부드러운 애니메이션이 눈에 띄는 끊김으로 변하기 때문입니다. 앱은 시간 예산 내에서 각 프레임을 엄격히 렌더링해야 합니다: 60 FPS의 경우 16.6ms, 90 FPS의 경우 11.1ms, 120 FPS의 경우 8.3ms입니다.

FPS는 UI뿐만 아니라 게임, 비디오 및 카메라에서도 측정됩니다. 게임에서 FPS는 장면 복잡성, 텍스처 품질 및 GPU 성능에 따라 달라집니다. 비디오에서 FPS는 고정되어 있으며(24, 30, 60fps) 콘텐츠에 의해 결정됩니다. 모바일 앱에서 FPS는 UI 코드 효율성(레이아웃 복잡성, 뷰 수, 다시 그리기 빈도, GC(Garbage Collection) 작업)에 따라 달라집니다. Apple WWDC 2022에 따르면, 비효율적인 컬렉션 업데이트(insert/delete/dequeueReusableCell 대신 reloadData)로 인해 앱의 평균 FPS가 10~15% 떨어질 수 있습니다. 실시간 FPS 측정은 성능 작업을 하는 QA 엔지니어와 개발자에게 표준 관행입니다.

FPS 계산 방법

모바일 앱에서 FPS 계산은 연속적인 프레임 간 시간 측정을 기반으로 합니다. 가장 간단한 공식: FPS = 1000 / deltaTimeMs, 여기서 deltaTimeMs는 이전 프레임 완료와 현재 프레임 완료 사이의 간격입니다. 현재 프레임이 20ms에 렌더링된 경우 FPS = 1000 / 20 = 50입니다. 그러나 실제로 FPS는 1초 내에서도 안정적인 경우가 거의 없습니다. 일반적인 프로필에는 12~16ms의 프레임에 건너뛴(jank) 프레임이나 느린 프레임(40~60ms)이 섞여 있습니다. 따라서 FPS는 1~5초에 걸친 이동 평균 또는 프레임 시간 분포의 백분위수로 측정됩니다.

Android에서 FPS는 Choreographer를 통해 계산되며, VSync(디스플레이 동기화 펄스)에서 콜백을 받습니다. 각 콜백은 하나의 프레임에 해당합니다. 콜백이 도착하지 않으면 프레임이 건너뜁니다. Choreographer를 사용하면 초당 정확한 프레임 수와 건너뛴 프레임 수를 측정할 수 있습니다. iOS에서 CADisplayLink는 유사하게 작동합니다. 디스플레이가 새 프레임을 렌더링할 준비가 될 때마다 호출됩니다. timestamp 속성에는 마지막 프레임의 정확한 시간이 포함되고, targetTimestamp에는 다음 프레임의 예상 시간이 포함됩니다. 두 시간의 차이가 현재 프레임의 시간 예산입니다.

CADisplayLink를 통한 FPS 모니터링

Swift 코드는 CADisplayLink를 통한 간단한 FPS 모니터링을 보여줍니다. frameCount 카운터는 호출될 때마다 증가하며, 1초에 한 번 실제 FPS가 계산됩니다.

swift
class FpsCounter {

    private var displayLink: CADisplayLink?
    private var frameCount = 0
    private var lastTime = TimeInterval(0)

    func start() {
        displayLink = CADisplayLink(
            target: self,
            selector: #selector(countFrame)
        )
        displayLink?.add(to: .current,
            forMode: .common)
    }

    @objc
    private func countFrame() {
        frameCount += 1
        let now = Date().timeIntervalSince1970

        if now - lastTime >= 1.0 {
            print("FPS: \(frameCount)")
            frameCount = 0
            lastTime = now
        }
    }
}

60 FPS가 표준인 이유

60 FPS(또는 60Hz) 표준은 여러 이유로 업계에 자리 잡았습니다. 첫째는 생리학적: 인간의 눈은 50~60Hz 이상의 주파수에서 개별 프레임을 구분하지 못하고 부드러운 움직임으로 인식합니다. 이 임계값을 임계 깜빡임 융합(CFF)이라고 합니다. 둘째는 역사적: 초기 음극선관(CRT)은 미국(NTSC)에서 60Hz, 유럽(PAL)에서 50Hz로 작동했습니다. 현대 LCD 디스플레이는 이 주파수를 계승했습니다. 셋째는 엔지니어링: UI 애니메이션의 경우 60 FPS는 서브밀리초 터치 응답 지연 시간을 제공하며, 이는 텍스트 입력, 스크롤 및 드래그에 중요합니다.

모바일 개발자에게 60 FPS는 단순한 권장 사항이 아니라 프레임당 16.6ms의 엄격한 예산입니다. 이 예산은 모든 렌더링 단계(입력(1~2ms), 애니메이션(2~3ms), 레이아웃(3~5ms), 그리기(3~5ms), 스왑(1~2ms))에 분배됩니다. 어떤 단계가 하위 예산을 초과하면 프레임이 16.6ms에 맞지 않을 수 있습니다. Google Android Performance는 프레임 준비에 12~14ms 이내를 유지하고 시스템 중단(GC, 백그라운드 스레드)에 2~4ms의 여유를 두도록 권장합니다. Firebase Performance에 따르면 평균 FPS가 52 미만이고 P99 FPS가 30 미만인 앱은 Google Play 리뷰에서 성능 관련 불만이 35% 더 많습니다.

FPS와 프레임 시간의 관계

FPS프레임 시간은 동일한 지표의 양면이며, 혼동하지 않는 것이 중요합니다. FPS는 속도, 프레임 시간은 지연 시간입니다. 60 FPS에서 각 프레임은 16.6ms가 걸립니다. 30 FPS에서는 33.3ms입니다. 그러나 FPS는 비선형 지표입니다. 60에서 30 FPS로 떨어지면 프레임 시간이 두 배가 되고, 30에서 20으로 떨어지면 1.5배 증가합니다. 따라서 프로파일러는 평균 주파수 대신 프레임 시간을 표시하여 문제가 있는 프레임을 확인할 수 있게 합니다. 예를 들어 평균 55 FPS는 5%의 프레임이 50~100ms의 프레임 시간을 가진다는 사실을 숨길 수 있습니다. 이러한 프레임은 Jank를 유발하지만 평균 FPS에는 크게 영향을 미치지 않습니다.

성능 분석 시 평균 FPS가 아닌 프레임 시간 히스토그램을 보는 것이 좋습니다. Android Studio Profiler 및 iOS Instruments에서 프레임 시간은 녹색 영역이 16.6ms(60 FPS)까지, 노란색이 16.6~33.3ms(30~60 FPS), 빨간색이 33.3ms 이상(30 FPS 미만)인 척도로 표시됩니다. 각 빨간색 열은 사용자에게 눈에 띄는 지연입니다. 실용적인 규칙: P95 프레임 시간(95%의 프레임이 Xms 이내)은 평균 FPS보다 더 신뢰할 수 있는 지표입니다. P95 프레임 시간이 32ms(30 FPS)를 초과하면 평균 FPS가 50이더라도 앱이 느리게 느껴집니다.

프레임 시간을 FPS로 변환

백분위수와 함께 프레임 시간 배열을 FPS로 변환하는 Kotlin 함수입니다. 자세한 분석을 위해 평균 FPS뿐만 아니라 P50, P90 및 P99도 반환합니다.

kotlin
data class FpsReport(
    val average: Float,
    val p50: Float,
    val p90: Float,
    val p99: Float
)

fun List<Long>.toFpsReport(): FpsReport {
    val fpsValues = this.map { ms ->
        if (ms > 0) 1000f / ms else 0f
    }.sorted()

    return FpsReport(
        average = fpsValues.average().toFloat(),
        p50 = fpsValues[fpsValues.size / 2],
        p90 = fpsValues[(fpsValues.size * 90 / 100)],
        p99 = fpsValues[(fpsValues.size * 99 / 100)]
    )
}

높은 FPS와 새로운 디스플레이

90, 120 및 144Hz 디스플레이를 갖춘 최신 모바일 기기는 FPS에 새로운 요구 사항을 부과합니다. 앱이 120Hz 디스플레이에서 60 FPS를 제공하면 화면 새로 고침의 두 번째 주기마다 동일한 프레임을 받기 때문에 사용자가 미세 끊김을 보게 됩니다. 120 FPS를 유지하려면 프레임당 예산이 16.6ms에서 8.3ms로 줄어들어 두 배 더 효율적인 렌더링 코드가 필요합니다. Android 개발자(Google I/O 2023)에 따르면 안정적인 120 FPS를 달성하려면 다음이 필요합니다: 그리기 주기에서 할당 방지, 계층 구조의 뷰 수 최소화(80 미만), 무거운 drawable 대신 VectorDrawable 사용, 복잡한 그래픽에 surfaceView 사용.

iOS의 상황도 유사합니다: ProMotion(120Hz)이 탑재된 iPhone Pro는 두 배의 프레임이 필요하지만 프레임당 시간은 절반으로 줄어듭니다. Apple은 모든 애니메이션이 120 FPS로 실행될 필요는 없다고 지적합니다. Core Animation은 정적이거나 느리게 변화하는 요소의 프레임률을 자동으로 낮춥니다. 그러나 스크롤, 제스처 애니메이션 및 전환은 “매끄러운” 느낌을 위해 120 FPS를 제공해야 합니다. 60에서 120 FPS로 전환할 때의 주요 문제: 전력 소비 증가(GPU 기준 25~40%), 기기 발열, 과열로 인한 프레임률 저하(스로틀링)입니다. 프레임 시간이 지속적으로 8.3ms를 초과하면 시스템 스로틀링을 기다리는 대신 프로그래밍 방식으로 목표 프레임률을 60 FPS로 낮추는 폴백 메커니즘을 구현하는 것이 좋습니다.

60/120 FPS 전환기

Android용 Java 코드는 기기가 120 FPS를 지원할 수 있는지 확인하고 렌더링 모드를 전환합니다. 지원되는 주사율을 확인하기 위해 Display.getMode가 사용됩니다.

java
class FpsModeSwitcher {

    static boolean canDo120Fps(Activity activity) {
        Display display = activity.getWindowManager()
            .getDefaultDisplay();
        for (Display.Mode mode : display.getSupportedModes()) {
            if (mode.getRefreshRate() >= 120f) {
                return true;
            }
        }
        return false;
    }
}

애플리케이션의 FPS 최적화

FPS 최적화는 프로파일링으로 시작하여 문제 영역의 리팩토링으로 끝나는 체계적인 접근 방식이 필요합니다. 첫 번째 단계는 프로파일러를 사용하여 현재 FPS를 측정하는 것입니다. 두 번째 단계는 예산을 초과하는 프레임을 찾는 것입니다. Android에서는 GPU Profiling 또는 Perfetto를 통해 수행할 수 있습니다. iOS에서는 Core Animation 템플릿과 함께 Instruments를 사용합니다. 세 번째 단계는 원인을 제거하는 것입니다: 오버드로우 감소, 뷰 계층 깊이 감소, 레이아웃 단계를 ConstraintLayout으로 대체, ViewHolder Recycling 추가, 무거운 계산을 백그라운드 스레드로 이동.

FPS 특화 최적화에는 다음이 포함됩니다: 프레임 페이싱 — 빠른 프레임과 느린 프레임의 “폭발”을 방지하기 위해 프레임 간 시간을 균등하게 분배하는 메커니즘입니다. Android에서는 고정 간격의 Choreographer.FrameCallback을 사용하여 프레임 페이싱을 구현할 수 있습니다. iOS에서는 CADisplayLink.preferredFrameRateRange가 동일한 작업을 수행합니다. 두 번째 방법은 트리플 버퍼링: 시스템이 두 개 대신 세 개의 버퍼를 사용하여 GPU가 이전 프레임이 해제될 때까지 기다리지 않고 다음 프레임 렌더링을 시작할 수 있게 합니다. Android는 필요할 때 자동으로 트리플 버퍼링을 활성화하지만, iOS에서는 개발자가 CAMetalLayer를 통해 명시적으로 요청할 수 있습니다. 세 번째는 텍스처 캐싱: 매 프레임마다 다시 로드하지 않도록 GPU 메모리에 비트맵을 캐싱합니다.

Choreographer를 통한 프레임 페이싱

Kotlin 예제는 16.6ms의 고정 간격으로 프레임 페이싱을 구현하는 방법을 보여줍니다. 시스템이 지연되어도 모든 콜백이 균일한 간격으로 도착합니다.

kotlin
class PacedFrameRenderer {

    private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
    private var lastFrameTime = 0L

    private val frameCallback =
        Choreographer.FrameCallback { frameTimeNanos ->
            val delta = frameTimeNanos - lastFrameTime
            if (delta >= targetDelta) {
                onFrame(delta)
                lastFrameTime = frameTimeNanos
            }
            Choreographer.getInstance()
                .postFrameCallback(this)
        }

    private fun onFrame(delta: Long) {
        // 프레임 렌더링
    }
}

자주 묻는 질문

사용자에게 편안한 FPS는 얼마인가요?

60 FPS는 모바일 앱에 편안한 수준입니다. 60과 120 FPS의 차이는 빠른 애니메이션(스크롤, 드래그) 중에만 고주사율 디스플레이에서 noticeable합니다. 30 FPS 미만은 불편합니다.

FPS와 프레임 시간은 어떤 관계인가요?

FPS = 1000 / FrameTime(ms). 프레임 시간이 16.6ms이면 FPS = 60입니다. 프레임 시간이 33.3ms이면 FPS = 30입니다. FPS보다는 프레임 시간을 모니터링하는 것이 좋습니다. 문제가 있는 프레임을 보여주기 때문입니다.

스크롤할 때 FPS가 떨어지는 이유는 무엇인가요?

스크롤할 때 시스템은 목록의 각 새 항목에 대해 LayoutDraw를 호출합니다. 뷰가 복잡하거나 레이아웃이 캐시되지 않았거나 무거운 drawable을 사용하는 경우 프레임 시간이 증가하고 FPS가 떨어집니다. 해결책은 ViewHolder 재활용과 플랫 계층 구조입니다.

iOS에서 FPS를 측정하는 방법은?

Core Animation 템플릿과 함께 Instruments를 사용합니다(실시간 FPS 표시). 프로그래밍 방식 측정을 위해 초당 프레임을 계산하는 CADisplayLink를 사용합니다. 프로덕션의 경우 MXAnimatoryMetric 메트릭과 함께 MetricKit을 사용합니다.

트리플 버퍼링이란 무엇이며 FPS에 어떤 영향을 미치나요?

트리플 버퍼링은 두 개 대신 세 개의 버퍼를 사용하여 GPU가 현재 VSync가 완료되기 전에 다음 프레임 렌더링을 시작할 수 있게 합니다. 이는 피크 부하를 완화하고 FPS 안정성을 향상시키지만 한 프레임의 지연 시간을 추가합니다.

요약

  • FPS는 인터페이스 부드러움의 핵심 지표이며, 목표값은 초당 60프레임입니다.
  • 프레임 시간은 FPS보다 더 정확한 지표이며, 특히 P95 및 P99 백분위수가 중요합니다.
  • 120Hz 디스플레이의 경우 프레임당 8.3ms 예산으로 120 FPS가 필요합니다.
  • FPS 하락의 주요 원인은 오버드로우, 깊은 뷰 중첩 및 그리기 주기의 할당입니다.
  • 프레임 페이싱트리플 버퍼링은 프레임 시간 불규칙성을 완화하는 데 도움이 됩니다.
  • FPS 프로파일링 — GPU Profiling(Android), Instruments Core Animation(iOS), Firebase Performance를 통해.
  • 대규모 사용자 불만 이전에 회귀를 식별하기 위해 프로덕션에서 P95 프레임 시간 모니터링이 중요합니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기