Skip to content

Commit 4654ecb

Browse files
authored
perf: fast-path comparison in ResponseMessage to skip JSON.stringify during streaming (open-webui#21884)
ResponseMessage compared the entire message object via JSON.stringify on every reactive tick to detect changes. During streaming, content changes on every token, making the two O(content_length) JSON.stringify calls always return different results — pure wasted work. Add a fast O(1) comparison on content and done fields first. When either differs (the common streaming case), skip straight to cloning. Only fall through to the expensive JSON.stringify comparison for infrequent changes like sources, annotations, or status updates.
1 parent 527d36e commit 4654ecb

1 file changed

Lines changed: 10 additions & 2 deletions

File tree

src/lib/components/chat/Messages/ResponseMessage.svelte

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@
121121
122122
let message: MessageType = JSON.parse(JSON.stringify(history.messages[messageId]));
123123
$: if (history.messages) {
124-
if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
125-
message = JSON.parse(JSON.stringify(history.messages[messageId]));
124+
const source = history.messages[messageId];
125+
if (source) {
126+
// Fast path: O(1) check on the fields that change most often (content during streaming, done at end)
127+
// Avoids 2x O(n) JSON.stringify calls that are always true during streaming anyway
128+
if (message.content !== source.content || message.done !== source.done) {
129+
message = JSON.parse(JSON.stringify(source));
130+
} else if (JSON.stringify(message) !== JSON.stringify(source)) {
131+
// Slow path: full comparison for infrequent changes (sources, annotations, status, etc.)
132+
message = JSON.parse(JSON.stringify(source));
133+
}
126134
}
127135
}
128136

0 commit comments

Comments
 (0)