사전 프로파일링
개념

프로파일링

gabury1고친 사람 github-actions[bot]

프로파일링은 프로그램이 도는 동안 시간과 메모리가 어디로 갔는지 재는 일입니다. 어디가 느린지 코드를 읽으며 짐작하는 대신, 돌려 놓고 세어서 범인을 짚습니다. 잰 뒤에는 몫이 큰 곳부터 고치고 같은 조건에서 다시 잽니다.

쉽고 빠른 이해

프로파일링은 프로그램을 돌려 놓고 시간이 어느 함수로 갔는지 세는 일입니다. 주문 조회가 느릴 때 원인이 데이터베이스 조회인지 응답을 만드는 과정인지 가려 줍니다.

짐작은 잘 빗나갑니다. 시간은 짧고 자주 불리는 함수에 쌓입니다. 몇 번 불리는지는 들어온 데이터가 정합니다. 엉뚱한 곳을 고치면 시간만 쓰고 응답은 안 빨라집니다.

일하는 모양은 셋입니다.

  1. 느린 상황을 다시 만들어 놓습니다
  2. 그 상태에서 재 주는 도구(프로파일러)를 붙여 함수별 몫을 모읍니다
  3. 큰 곳을 한 군데 고치고 같은 조건에서 다시 잽니다

어느 요청이 느린지조차 모를 때는 아직 이릅니다. 그건 다른 도구가 먼저 알려 줍니다.

대가가 있습니다. 재느라 프로그램이 굼떠집니다. 개발 기계에서 잰 결과가 운영 서버의 답이 아닐 때도 많습니다.

상세

여행 가방이 무게 제한을 넘으면 짐을 전부 다시 싸지 않습니다. 물건을 하나씩 저울에 올려 보고 무거운 것부터 뺍니다. 프로파일링이 프로그램에 하는 일이 이것입니다.

프로파일링은 프로그램이 도는 동안 함수마다 시간이나 메모리를 얼마나 썼는지 모아서, 몫이 큰 곳부터 고치는 일입니다. 재는 도구가 프로파일러입니다. 그 결과로 나온 기록이 프로파일입니다. 주문 조회가 느리다면, 늦어진 몫이 데이터베이스 조회에 있는지 응답을 만드는 과정에 있는지를 이 일이 가려 줍니다.

코드를 읽어서 범인을 짚으면 대개 빗나갑니다. 시간은 복잡해 보이는 함수가 아니라 자주 불리는 짧은 함수에 쌓입니다. 몇 번 불리는지는 코드가 아니라 들어온 데이터가 정합니다. 그래서 고치기 전에 재는 단계를 따로 둡니다.

재고 고치고 다시 재는 한 바퀴

프로파일링은 한 번 재고 끝나는 일이 아닙니다. 고친 것이 정말 빨라졌는지는 같은 조건에서 다시 재야 알 수 있습니다. 그래서 잰다는 말 안에는 고친 뒤 또 잰다는 뜻이 들어 있습니다.

flowchart TD
    A["무엇이 얼마나 느린지 정한다"] --> B["느린 상황을 다시 만든다"]
    B --> C["프로파일러를 붙여 잰다"]
    C --> D["몫이 큰 곳을 고른다"]
    D --> E["한 군데만 고친다"]
    E --> F["같은 조건에서 다시 잰다"]
    F -->|아직 멀면| D
    F -->|목표에 닿으면| G["멈춘다"]

고리의 앞쪽 절반은 준비이고 뒤쪽 절반은 확인입니다. 목표를 정하고 느린 상황을 다시 만든 다음에야 잰 값이 뜻을 가집니다. 고친 뒤 같은 조건에서 다시 재서 나아진 폭을 확인합니다. 목표에 닿으면 멈춥니다.

재기 전에 정하는 것

재기부터 시작하면 숫자만 쌓이고 결론이 안 납니다. 무엇이 느린지와 어디까지 빨라야 하는지를 먼저 적어 둡니다. 「주문 조회가 느리다」가 아니라 「주문 조회 응답을 지금의 절반으로」처럼 끝을 정해야 멈출 때를 압니다.

그다음은 재현입니다. 같은 입력, 같은 데이터 양, 같은 순서로 그 느린 상황을 다시 만들어 놓아야 합니다. 재현이 안 되면 잴 때마다 값이 달라져서 고치기 전과 뒤를 견줄 수 없습니다.

처음 몇 번은 원래 느립니다. 캐시가 아직 비어 있고 런타임도 덥혀지지 않았기 때문입니다. 이 구간을 워밍업이라고 부릅니다. 재는 값에서 이 구간은 빼는 것이 보통입니다.

잣대 넷 — 시간·메모리·잠금 대기

느린 원인이 늘 계산은 아닙니다. 기다림일 수도 있고, 메모리를 너무 많이 만드는 것일 수도 있고, 서로 잠금을 기다리는 것일 수도 있습니다. 무엇을 잣대로 삼느냐에 따라 프로파일링이 내놓는 답이 달라집니다.

재는 것 찾아내는 것
CPU(Central Processing Unit, 중앙처리장치) 시간 — 계산에 쓴 시간 계산에 시간을 쓰는 함수
벽시계 시간 — 시작해서 끝날 때까지 그냥 흐른 시간 응답이나 파일을 기다리느라 늦어진 구간
메모리 할당 더 안 쓰게 된 객체(쓰레기)를 많이 만드는 곳과 쌓이기만 하는 할당
잠금 대기 서로 기다리느라 못 나아가는 구간

앞의 두 잣대는 같은 요청의 서로 다른 구간을 봅니다. 요청 한 건의 시간을 띠로 늘어놓으면 어디가 갈리는지 보입니다.

block-beta
  columns 4
  W["벽시계 시간 — 시작해서 끝날 때까지"]:4
  A["계산"] B["기다림"]:2 C["계산"]
  D["CPU 시간에 센다"] E["안 센다"]:2 F["CPU 시간에 센다"]

잣대를 잘못 고르면 범인이 안 보입니다. 늦어진 까닭이 기다림인데 계산한 시간만 세는 방식으로 재면, 그 요청은 결과에 거의 안 나타납니다. 그래서 무엇을 재는 중인지를 먼저 알고 붙입니다.

개발 기계와 운영 서버

잰 값은 잰 조건에서만 참입니다. 개발 기계와 운영 서버는 데이터 양도 다르고 동시에 들어오는 요청 수도 다릅니다. 조건이 다르면 범인도 바뀝니다. 재는 일 자체도 프로그램을 굼뜨게 합니다. 그 굼뜸을 견딜 여유가 두 곳에서 다릅니다.

개발 기계 운영 서버
데이터 양 몇백 건 수천만 건까지
동시에 들어오는 요청 나 하나 여럿이 겹친다
재는 방법 부담이 커도 된다 부담이 적은 방법으로 성기게

개발 기계에서는 목록을 훑는 함수가 눈에 안 띕니다. 항목이 몇백 개뿐이라 금방 끝나기 때문입니다. 같은 함수가 운영에서는 수천만 건을 훑으며 응답 시간의 대부분을 먹습니다.

재는 행위에도 값이 듭니다. 멈춰 세우고 기록하는 데 시간이 듭니다. 그 몫이 결과에 섞입니다. 그래서 운영에서 재려면 부담이 적은 방법을 고르고 재는 간격을 성기게 둡니다. 이렇게 조금씩 늘 재 두는 것을 상시 프로파일링이라고 부릅니다.

고칠 곳을 고르는 잣대

결과를 받으면 눈에 거슬리는 함수부터 고치고 싶어집니다. 그러나 고를 잣대는 하나입니다. 전체에서 차지하는 몫이 큰지만 봅니다.

작은 몫은 아무리 줄여도 전체 시간이 거의 안 줄어듭니다. 이 한계를 암달의 법칙이라고 부릅니다.

읽을 때는 두 가지 시간을 갈라 봐야 합니다. 그 함수 자신이 쓴 시간이 자기 시간입니다. 그 함수가 부른 것까지 합친 시간이 누적 시간입니다.

flowchart TD
    A["A 함수"]
    subgraph N["B 의 누적 시간 — B 와 B 가 부른 것을 합친다"]
        B["B 함수 — 자기 시간"]
        C["C 함수"]
        B --> C
    end
    A --> B

합친 시간만 보면 맨 위 함수가 늘 1등입니다. 정작 시간을 쓴 아래쪽 함수가 그 뒤에 가려 안 보입니다. 둘을 가르는 법은 프로파일러 편이 자세히 다룹니다.

고칠 때는 한 번에 한 군데입니다. 둘을 같이 고치면 어느 쪽이 효과를 냈는지 모릅니다. 한쪽이 오히려 느려져도 다른 쪽에 가려 안 보입니다. 하나 고치고 다시 재는 편이 결국 빠릅니다.

고치기 전과 뒤의 값은 적어 둡니다. 나중에 같은 요청이 다시 느려졌을 때, 그 기록이 있어야 새로 생긴 성능 회귀인지 원래 그랬던 것인지 가릅니다.

프로파일링을 꺼낼 때와 안 꺼낼 때

무엇이 느린지조차 모르는 단계에서는 이릅니다. 요청마다 걸린 시간을 숫자로 쌓아 두는 것이 메트릭입니다. 어느 요청이 오래 걸리는지는 메트릭이 먼저 알려 줍니다.

그 숫자를 지켜보다가 늦어지면 알려 주는 것이 모니터링입니다. 느려졌다는 신호는 모니터링이 줍니다. 프로파일링은 그다음입니다. 느린 요청을 이미 알고, 범인이 어느 함수인지까지 좁힐 때 꺼냅니다.

요청 하나가 프로그램 여럿을 거칠 때가 있습니다. 따로 떠서 서로를 부르는 그 프로그램 하나하나가 서비스입니다.

요청 하나가 어느 서비스를 거쳤고 각각에서 얼마나 머물렀는지 이어 붙인 기록이 트레이스입니다. 어느 서비스가 시간을 썼는지는 트레이스가 보여 줍니다.

프로파일링이 보는 범위는 프로세스 하나의 안쪽입니다. 프로세스는 지금 돌고 있는 프로그램 하나를 가리킵니다. 그래서 서비스를 고른 다음에 그 안에서 씁니다. 셋은 차례로 이어지는 것이 아니라 보는 범위가 서로 포개집니다.

flowchart TD
    subgraph S["시스템 전체 · 모니터링 · 메트릭 — 어느 요청이 느린가"]
        subgraph R["요청 하나가 거치는 서비스들 · 트레이스 — 어느 서비스가 시간을 썼나"]
            subgraph P["프로세스 하나 · 프로파일링 — 어느 함수가 시간을 썼나"]
                F["함수"]
            end
        end
    end

아직 안 느린 코드에는 쓰지 않습니다. 재고 고치는 동안 코드는 읽기 어려워집니다. 느리지도 않은 것을 빠르게 만들면 그 대가만 남습니다. 느려졌다는 신호를 먼저 받고 시작합니다.

관련 항목

프로파일링에 쓰는 도구와 그 결과물

프로파일러 · 프로파일 · 플레임 그래프 · perf · 힙 덤프 · 호출 그래프

프로파일링이 자원을 재는 방식

샘플링 · 계측 · 상시 프로파일링 · 하드웨어 성능 카운터 · 추적

프로파일링이 세는 자원과 지표

CPU 시간 · 벽시계 시간 · 자기 시간 · 누적 시간 · 메모리 할당 · 잠금 경합

프로파일링으로 찾아내는 성능 문제

병목 · 핫스팟 · 메모리 누수 · N+1 문제 · 캐시 미스 · 꼬리 지연

프로파일링 앞뒤에 서는 측정 활동

모니터링 · 메트릭 · 트레이스 · 스팬 · 벤치마크 · 부하 테스트 · 로그

프로파일링 결과를 흔드는 실행 조건

워밍업 · 인라이닝 · 가비지 컬렉션 · 런타임 · 컴파일러 최적화 · 관측자 효과

잰 뒤에 이어지는 성능 개선 수단

성능 튜닝 · 리팩터링 · 캐싱 · 암달의 법칙 · 성능 회귀

프로파일링이 속하는 상위 분야

성능 · 성능 분석 · 관측성 · 성능 공학

다른 이름: profiling · 프로파일링 작업