[Open WebUI] ResponseMessage에서 JSON.stringify 비교를 O(1) fast-path로 우회
PR 링크: open-webui/open-webui#21884 상태: Merged | 변경: +10 / -2
들어가며
Open WebUI의 ResponseMessage.svelte는 Svelte의 리액티브 구문($:)으로 히스토리 변경을 감지하고, 변경이 있으면 메시지 객체를 딥 카피합니다. 문제는 변경 감지 자체가 JSON.stringify()를 2번 호출하는 O(n) 연산이라는 점입니다. 스트리밍 중에는 content가 매 토큰마다 바뀌므로, 이 비교는 항상 다른 결과를 반환하면서 순수한 낭비입니다. 이 PR은 O(1) fast-path를 추가합니다.
핵심 코드 분석
Before: 매번 전체 비교
$: if (history.messages) {
if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
message = JSON.parse(JSON.stringify(history.messages[messageId]));
}
}
스트리밍 중에는 content가 매 토큰마다 변경되므로, JSON.stringify() 2회 + JSON.parse(JSON.stringify()) 1회 = 총 3회의 직렬화가 무조건 실행됩니다.
After: O(1) fast-path 추가
$: if (history.messages) {
const source = history.messages[messageId];
if (source) {
// Fast path: O(1) - 스트리밍 중 가장 빈번한 변경
if (message.content !== source.content || message.done !== source.done) {
message = JSON.parse(JSON.stringify(source));
} else if (JSON.stringify(message) !== JSON.stringify(source)) {
// Slow path: 드문 변경 (sources, annotations, status 등)
message = JSON.parse(JSON.stringify(source));
}
}
}
content와 done 필드의 참조 비교는 O(1)입니다. 스트리밍 중에는 거의 항상 content가 다르므로 fast-path에서 바로 딥 카피로 진행하고, 비용이 큰 JSON.stringify() 비교를 건너뜁니다.
왜 이게 좋은가
1. 완전한 "공짜 점심"
이 변경은 동작 결과가 완전히 동일합니다. 변경 감지 방식만 최적화했으므로 사이드 이펙트가 없습니다.
2. 스트리밍 핫 패스에서의 절감
초당 30개 토큰을 생성하는 LLM 응답에서, 기존에는 초당 60회의 JSON.stringify()가 실행되었습니다. 응답이 2000자 정도면 매초 약 120KB의 임시 문자열을 생성하고 버리는 셈입니다. fast-path로 이 비용이 0이 됩니다.
3. 점진적 최적화 전략
이 PR은 후속 PR(#21948)에서 JSON.parse(JSON.stringify())를 structuredClone()으로 교체하는 작업의 전 단계입니다. 먼저 감지 비용을 줄이고, 다음으로 복사 비용을 줄이는 단계적 접근입니다.
참고 자료
- V8 Blog: Cost of JSON.stringify() — JSON 직렬화의 CPU 비용
- Open WebUI PR #21948 — 후속 structuredClone 최적화
관련 포스트
- [Open WebUI] CodespanToken에서 JS 트랜지션을 CSS 애니메이션으로 교체하여 메인 스레드 부하 제거
- [Open WebUI] TTS 문장 파싱을 showCallOverlay 가드로 감싸 불필요한 O(n^2) 연산 제거
- [Open WebUI] CodeEditor에서 EditorView 미해제로 인한 메모리 누수 수정
- [Open WebUI] UserMessage에서 JSON 직렬화 대신 structuredClone과 빠른 경로 비교 적용
- [Open WebUI] JSON.parse(JSON.stringify()) 를 structuredClone으로 교체
PR Analysis 의 다른글
- 이전글 [faster-qwen3-tts] 생성 요청 직렬화 및 모델 캐싱 도입
- 현재글 : [Open WebUI] ResponseMessage에서 JSON.stringify 비교를 O(1) fast-path로 우회
- 다음글 [faster-qwen3-tts] Windows 네이티브 셋업 및 벤치마크 스크립트 추가
댓글