☰ Categories

Read a profile and decide what to optimize

Locates where time actually goes in the measurements and prevents optimizing by guess.

CategoryDevelopment › Coding
TagsAnalyzingCodeDeveloper
Prompt
Read this profile and plan the optimization.

Produce:
1. Where time actually goes, as percentages of the total. *Reason only from the measurements. Do not rank by what looks inefficient in the code.*
2. For each of the top three costs: **the end-to-end gain if it were eliminated entirely.** A function taking 8% of the time cannot make the request more than 8% faster — this ceiling decides whether it is worth touching.
3. Distinguish self time from cumulative time. Reading these the wrong way round is the most common misreading of a profile.
4. Whether the cost is algorithmic, I/O bound, allocation, contention, or startup. Each has a different fix and only one of them is helped by faster code.
5. **Calls that should not be happening at all** — repeated work, N+1 patterns, work done in a loop that could be hoisted. Removing a call beats optimizing it.
6. Ordered plan: effort versus gain, with the target in view. Say where to stop.

Rules:
- *If the profile does not explain the latency I am seeing, say so* and name what to measure next — the bottleneck may be outside what was profiled.
- Say when the answer is "this is already fast enough, do not optimize".
- Note anything that would make the code harder to maintain for a gain below a few percent, and advise against it.
After pasting, fill in the fields at the bottom (Profile · Relevant code · Target)

What this prompt does

Most optimization work is spent in the wrong place. This reasons only from measurements and computes the end-to-end gain first, so you can tell whether a fix is worth doing at all.

Model comparison

ChatGPT best controls uncertainty. Gemini is concrete but makes unsupported attributions and latency predictions, while [C] is missing.

ChatGPTTop overall
43/ 50

+ Carefully distinguishes self and cumulative time.

- Residual-time math is off and overlap wording is unclear.

Gemini
36/ 50

+ Quantifies ceilings and gives a concrete execution order.

- Invents residual attribution and a 20–50 ms outcome.

CriterionChatGPTGeminiLeader
Instruction following98ChatGPT +13%
Accuracy85ChatGPT +60%
Specificity99Tie
Structure98ChatGPT +13%
Right length86ChatGPT +33%

Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-24). This is an AI review, not a measurement.

Read full answers

We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.

Input used
Profile: cProfile 결과 (Django, GET /api/orders/ 요청 1회):

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    12045    3.812    0.000    3.812    0.000 {method 'execute' of 'psycopg2.extensions.cursor' objects}
     8901    2.104    0.000    2.104    0.000 {built-in method _socket.socket.recv}
    45000    1.203    0.000    1.950    0.000 orders/serializers.py:88(get_discount_price)
        1    0.045    0.045    9.812    9.812 orders/views.py:34(OrderListView.list)
    45000    0.611    0.000    0.611    0.000 {method 'strftime' of 'datetime.datetime' objects}
    12045    0.302    0.000    4.114    0.000 orders/models.py:112(Order.get_items)

Total time: 9.812s, HTTP 200

Relevant code: # orders/views.py
class OrderListView(APIView):
    def get(self, request):
        orders = Order.objects.filter(store_id=request.user.store_id)
        data = OrderSerializer(orders, many=True).data
        return Response(data)

# orders/serializers.py
class OrderSerializer(serializers.ModelSerializer):
    discount_price = serializers.SerializerMethodField()

    def get_discount_price(self, obj):
        # 쿠폰을 주문 건마다 매번 새로 조회
        coupon = Coupon.objects.filter(store=obj.store, active=True).first()
        return obj.price - (coupon.amount if coupon else 0)

Target: 현재 주문 목록 API 평균 응답 9.8초, 피크 시간대 동시 요청 50건까지는 버텨야 합니다. 300ms 이내로 줄이는 게 목표예요.
gpt-5.6-luna (codex CLI · 에이전트 하네스) · Run on 2026-09-24

프로파일만 보면 평균 9.8초의 원인은 명확합니다. 다만 목표인 300ms는 단순한 Python 최적화로는 달성할 수 없습니다. DB 호출 수와 반환 데이터 규모를 크게 줄여야 합니다.

1. 시간 분포

Total time = 9.812초 기준입니다. tottime은 해당 함수 자체 시간이며, 자식 호출 시간은 제외합니다.

항목 self time 전체 비율 cumulative time 해석
cursor.execute 3.812초 38.9% 3.812초 SQL 실행 대기
socket.recv 2.104초 21.4% 2.104초 DB/네트워크 응답 수신 대기
get_discount_price 1.203초 12.3% 1.950초 할인 계산 및 내부 호출
strftime 0.611초 6.2% 0.611초 날짜 문자열 변환
Order.get_items 0.302초 3.1% 4.114초 자체 시간은 작지만 하위 SQL 포함
기타 호출/프레임워크 약 1.78초 약 18.1% — 직렬화, Django, 호출 오버헤드 등

get_items의 cumulative 4.114초는 execute 시간을 포함합니다. 따라서 4.114초 + 3.812초처럼 더하면 안 됩니다.

마찬가지로 get_discount_price의 cumulative 1.950초는 내부 호출 시간을 포함하므로, 독립적인 추가 비용은 self time인 1.203초에 가깝습니다.

2. 상위 세 비용과 제거 시 이론상 최대 이득

1위: DB execute

  • self time: 3.812초
  • 전체의 38.9%
  • 전부 제거해도 이론상 최대 약 38.9% 단축
  • 9.812초 → 약 6.000초

이는 알고리즘 문제가 아니라 DB I/O 비용입니다. SQL 개수가 12,045회라는 점이 핵심입니다. 인덱스 하나보다 먼저 쿼리 수를 줄여야 합니다.

2위: socket.recv

  • self time: 2.104초
  • 전체의 21.4%
  • 전부 제거해도 이론상 최대 약 21.4% 단축
  • 9.812초 → 약 7.708초

이 역시 Python 코드가 느린 것이 아니라 응답을 기다리는 I/O입니다. DB 처리 시간, 반환 행 수, 네트워크 왕복 횟수와 관련 있을 가능성이 큽니다.

execute와 recv는 서로 완전히 독립적인 비용이라고 볼 수 없으므로 38.9%와 21.4%를 단순 합산하면 안 됩니다.

3위: get_discount_price

  • self time: 1.203초, 12.3%
  • cumulative time: 1.950초, 19.9%
  • 함수와 하위 작업을 통째로 제거한다는 의미라면 최대 약 19.9%
  • 함수 자체의 Python 비용만 제거한다면 최대 약 12.3%

호출 횟수가 45,000회입니다. 주문마다 쿠폰을 조회하는 코드와 일치하는 전형적인 N+1 패턴입니다. 이 비용은 단순히 함수 내부를 미세 최적화할 대상이 아니라, 호출 구조 자체를 제거해야 하는 대상입니다.

3. 비용의 성격

  • cursor.execute: DB I/O bound
  • socket.recv: 네트워크/DB 응답 I/O bound
  • get_discount_price: 반복 작업 및 N+1 가능성
  • strftime: 반복적인 CPU 변환 비용
  • Order.get_items: cumulative 기준으로는 DB 호출을 포함한 데이터 접근 비용
  • 45,000회 직렬화/날짜 변환: 반복 호출과 객체/문자열 생성 비용

현재 측정으로는 “느린 알고리즘”이 주된 원인이라고 보기 어렵습니다. 빠른 Python 코드로 바꿔도 DB 대기와 네트워크 대기가 남습니다.

4. 발생하지 않아야 할 호출

가장 명확한 문제는 이것입니다.

coupon = Coupon.objects.filter(
    store=obj.store,
    active=True
).first()

이 코드가 주문마다 실행됩니다. 45,000번의 할인 계산 호출이 있으므로, 쿠폰 조회도 주문 수에 비례해 반복될 가능성이 큽니다.

개선 방향은 다음 우선순위입니다.

  1. 매장별 활성 쿠폰을 한 번만 조회합니다.
  2. 주문 목록을 직렬화하기 전에 매장별 쿠폰을 미리 로드합니다.
  3. 가능하면 주문 쿼리와 쿠폰 쿼리를 배치 처리합니다.
  4. 동일한 매장에 대한 쿠폰 조회를 요청 단위 캐시로 재사용합니다.
  5. get_items가 주문마다 추가 쿼리를 만든다면 prefetch_related 또는 집계 쿼리로 바꿉니다.

단, 45,000회라는 수치와 현재 예시 코드의 주문 조회 구조 사이에는 확인이 필요합니다. 실제로는 페이지네이션 없이 매우 많은 주문을 반환하고 있거나, serializer 내부에 추가적인 반복 호출이 있을 가능성이 있습니다.

5. 300ms 목표에 대한 판단

현재 9.812초에서 300ms는 약 97% 단축입니다.

상위 비용 하나를 완전히 제거해도:

  • execute 제거: 약 6.0초
  • socket.recv 제거: 약 7.7초
  • 할인 함수 전체 제거: 약 7.9초

따라서 상위 한두 함수를 최적화하는 정도로는 목표에 도달하지 못합니다. 특히 45,000개 레코드를 한 번에 직렬화하고 있다면, 300ms 목표와 응답 형태 자체가 충돌할 수 있습니다.

필수 조치는 다음과 같습니다.

  • 페이지네이션 도입
  • 반환 필드 축소
  • 주문별 쿼리 제거
  • 쿠폰 조회 배치화 또는 select_related/prefetch_related
  • get_items의 쿼리 수와 SQL 실행 계획 확인
  • 날짜 포맷팅을 반환 행 수에 비례하지 않도록 줄이거나 필요한 필드만 변환
  • DB 인덱스와 실제 SQL 실행 시간 확인

6. 권장 실행 순서

  1. 쿼리 수부터 줄이기 — 가장 높은 우선순위

    12,045회 execute를 목표로 수십 회 이하로 줄입니다. 쿠폰 N+1과 get_items의 추가 쿼리를 먼저 확인합니다.

  2. 페이지네이션과 응답 크기 제한

    45,000회 serializer 호출이 실제 응답 데이터 규모를 의미한다면, 300ms 목표를 위해 한 요청의 처리 건수를 제한해야 합니다.

  3. SQL 실행 계획과 인덱스 확인

    쿼리 수를 줄인 후에도 개별 SQL이 느리면 EXPLAIN ANALYZE, 필터 컬럼 인덱스, 정렬 조건을 확인합니다.

  4. 직렬화 비용 측정 및 축소

    strftime과 할인 계산은 DB 비용을 해결한 뒤 처리합니다. 이 단계의 최댓값은 현재 약 1~2초 수준이므로 단독으로는 목표를 만들 수 없습니다.

  5. 재프로파일링

    SQL 수, 총 DB 시간, 응답 바이트 수, serializer 호출 수를 다시 측정합니다.

현재 프로파일은 서버 내부 요청 처리 시간을 설명합니다. 만약 사용자가 보는 9.8초가 이 값과 다르다면, 다음도 별도로 측정해야 합니다.

  • 웹 서버/프록시 대기 시간
  • DB 서버에서 실제 실행된 총 시간
  • DB 연결 획득 시간
  • 응답 직렬화 후 전송 시간
  • 응답 크기와 클라이언트 수신 시간
  • 동시 요청 50건에서의 DB lock/connection-pool 대기

결론적으로, strftime 미세 최적화나 serializer 메서드의 Python 문법 변경은 권하지 않습니다. 전체의 몇 퍼센트 미만만 줄이고 유지보수성을 떨어뜨릴 가능성이 큽니다. 먼저 N+1 제거, 쿼리 배치화, 페이지네이션을 적용하고, 그 결과가 300ms에 근접했을 때만 남은 비용을 최적화해야 합니다.

More in this category

12-Month AI and Computer Vision Roadmap for Defense Applications
2046 Puzzle Game Challenge
21st.dev component prompt
3D FACTORY
3D FPS Game