Unity DevConsole 프리징 개선 기록 : 반복 에러 콜랩스 + Flush 예산화
1) DevConsole의 기존 작동방식과 핵심 기술
이 DevConsole은 유니티 로그를 받아서 콘솔 UI에 텍스트로 뿌리는 구조다.
핵심 흐름은 아래와 같았다.
- Application.logMessageReceivedThreaded 콜백으로 로그를 수신한다.
- 유니티는 싱글쓰레드이기에 백그라운드 상의 멀티 쓰레드의 로그를 모두 수집하면 데드락 위험이 있어 lock 키워드를 통해 pending 버퍼에 로그를 쌓는 방식으로 데드락을 방지했다.
- 콘솔이 열리면 버퍼에 쌓인 로그를 실제 콘솔 UI에 출력하는 lines 리스트에 옮긴다.
- lines 전체를 StringBuilder에 합쳐서 출력한다.
추가로, “연속 중복(바로 직전 로그와 동일)”은 체크해서 count++ 형태로만 병합했다.
그래서 연속해서 같은 에러가 나오는 상황에서는 어느 정도 줄어드는 효과가 있을 것이라 예상했다.
2) 문제 발생: 장문의 반복 에러 + 콘솔 오픈 순간 병목
문제는 “연속 중복”이 아닌 형태로 반복이 터질 때다.
예를 들어 같은 예외가 프레임마다 계속 찍히지만, 중간에 다른 로그가 섞이거나 스택/메시지가 조금씩 달라지면 pending이 계속 커진다.
특히 병목이 크게 터지는 지점은 콘솔을 키는 순간이다.
- pending이 많아진 상태에서 버퍼에서 라인으로 옮겨 출력하는 메서드들이 한 번에 몰아서 실행된다
- BuildText()가 lines 전체를 매번 문자열로 재구성한다
- 기존 코드에서 pending → lines 병합 시 count만큼 반복하는 루프가 있다
- 즉, “반복 수가 커질수록” 병합 비용이 기하급수적으로 늘어난다
결과적으로 “콘솔을 켰을 뿐인데 프레임이 몇 초 멈추는 현상"이 발생했다.
3) 해결 아이디어 정리
문제를 정확히 쪼개면 두 가지다.
1. 반복 로그를 출력 순간에 합치니 부하가 생긴다.
- 콘솔을 열 때 몰아서 처리하니까 스파이크가 생긴다.
- 해결 방향: 로그가 들어오는 시점에서 “동일 로그”를 바로 집계(콜랩스)한다.
2. Flush/병합을 한 프레임에 다 한다
- 버퍼 이동, 병합, 문자열 출력을 한 프레임에 다 처리하니 부하가 한 곳에 몰려 프리징 현상이 생긴다.
- 해결 방향: 프레임 예산(time/개수)을 두고 나눠 처리한다
부가로, 기존 count만큼 반복해서 증가시키는 로직도 꼭 필요해 보이진 않았다.
count를 통째로 더하는 AddCount()로 O(1) 병합하는 방향으로 수정해보자.
4) 해결법 실적용
(1) pending을 List → Dictionary 집계로 변경한다
동일 로그를 구분하는 키를 만든다.
- Key 구조체: LogType + msg + stack
- 문자열 비교지만 해시를 미리 계산해둬서 딕셔너리 조회를 빠르게 한다
pending에 쌓는 방식이 이렇게 바뀐다.
- 기존: List<LogItem> _pending에 계속 append
- 변경: Dictionary<LogKey, LogItem> _pendingDict에서 동일 키면 count++, 없으면 새 항목 추가
여기서 표시 순서는 유지하고 싶으니 별도의 _pendingOrder로 키 삽입 순서를 기록한다.
(2) Flush를 프레임 예산으로 쪼갠다
콘솔이 열렸을 때 pending을 lines로 옮기는 작업을 한 번에 하지 않는다.
- MaxFlushPerFrame: 프레임당 최대 처리 개수
- MaxFlushTimeMs: 프레임당 최대 처리 시간(ms)
이렇게 하면 pending이 아무리 많아도 프리징 대신 천천히 반영된다.
모든 로그를 꼭 한 번에 출력할 필요가 없으며, 애초에 보여줘도 인간은 인식하지도 못한다.
UI는 즉시 열고 [Loading logs...] 같은 메시지를 먼저 보여주면 체감도 좋아진다.
웹 프론트엔드에서 사용하던 Lazy Loading 에서 출발한 아이디어다.
(3) count 병합을 O(1)로 바꾼다
기존에는 병합 시 for (k < it.count) Increment()로 반복했는데, 이게 반복 폭탄에서 최악이었다.
이를 아래처럼 바꾼다.
- AddCount(now, add)로 count += add를 한 번에 처리
- 반복 수가 많아도 병합 비용은 동일하다
(4) 요약
- 수집한 쓰레드를 List에 append하지 않고 dict에 집계한다.
- flush 연산을 분할시켜서 부하를 여러 프레임에 분산시킨다.
- Increment 반복 제거 → AddCount로 한 번에 더한다.
5) 기대효과
이 방식으로 기대하는 효과는 다음과 같다.
- 콘솔을 켜도 순간적인 프리징 현상이 일어나지 않는다.
- 반복 에러는 “동일 키 1줄 + count 증가”로 수렴한다.
- pending이 길게 쌓여도 오픈 순간에 폭발하지 않는다.
- 최악 상황에서 성능이 “스파이크”가 아니라 “점진 처리”로 바뀐다.
사실상 부하 문제를 완전히 없애는 것이 아니라 통제 가능한 비용으로 바꾼 것에 가깝다.
6) 결과
- 프리징 현상을 수치로 기록하지 않았던 것은 아쉽지만 명백히 프리징이 발생한다, 발생하지 않는다로 관측할 수 있었다.
- 에러 로그가 과도하게 쌓였을때 콘솔을 열어도 더이상 프리징 현상이 발생하지 않는 것을 확인했다.
- 가시적으로 프리징 현상이 사라졌기 때문에 별도의 성능 차 기록은 생략했다.
'Unity' 카테고리의 다른 글
| Unity : C#과 Unity에서의 암묵적 Boolean 값 변환 => if (gameObject) 가능?? (0) | 2026.03.02 |
|---|---|
| Unity : 같은 PC에서 클라이언트 여러개 실행시 멈추는 현상 (Firestore) (0) | 2026.02.08 |
| Unity : Firebase 등록 및 Auth로 (이메일/소셜) 로그인 구현 (0) | 2026.01.11 |
| Unity Network : Photon Pun2 - PhotonView, IPunObservable, RPC (0) | 2026.01.07 |
| Unity Network : Photon Pun 2 - Lobby / Room (0) | 2026.01.07 |