Open Bug 1899468 Opened 4 months ago Updated 2 months ago

Twitch chat unusably slow and using 25% CPU after a few days on a stream

Categories

(Core :: JavaScript Engine, defect, P3)

Firefox 128
x86_64
Linux
defect

Tracking

()

Performance Impact medium
Tracking Status
firefox128 --- affected

People

(Reporter: ahale, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: perf:responsiveness, top50)

Attachments

(1 file)

I've been keeping a twitch.tv stream open for a few days in Firefox Nightly 128.0a1 on my Ubuntu 24.04 Linux computer and the chat has become progressively less responsive (to the point that it only types about 2 characters per second if I try to type anything), and the CPU usage of the tab is 25-30% according to about:processes on a fast CPU (Ryzen 5950X), when the tab is not visible it is still 7-17% CPU when idle. Nothing is being said in the chat during this time (it's a very small stream and largely idle).

Attached file about:support (text)
OS: Unspecified → Linux
Hardware: Unspecified → x86_64

profiler: https://share.firefox.dev/3R3R1y4

In this profile I simply typed 3 characters and backspaced over them, no chat messages posted or anything going on.

It looks like it is taking hundreds of ms to insertText and deleteBackward.
"Stack Chart" during the red jank bars shows long sections of time stuck "within" these call stack, though it seems to fractal out to a lot of different (minified) JS further down the stack chart. This feels like the website behaving badly.

Alternatively/possibly we are missing an optimization that other engines have that makes it preform poorly for us, though?

I will note that I have uBlock which may interact with Twitch, I tried loading a stream with uBlock disabled and it had the same degradation after ~24h though.

Summary: Twitch chat unusuably slow and using 25% CPU after a few days on a stream → Twitch chat unusably slow and using 25% CPU after a few days on a stream

The Performance Impact Calculator has determined this bug's performance impact to be medium. If you'd like to request re-triage, you can reset the Performance Impact flag to "?" or needinfo the triage sheriff.

Impact on site: Causes noticeable jank
Websites affected: Major

Performance Impact: --- → medium
Component: Performance → JavaScript Engine

The JS execution in that profile is not obviously pathological. Memory consumption seems very reasonable. We trigger two major GCs, but neither of them is during the janky parts, which means that we're doing a pretty good job of pushing them to idle time. It looks like about 3/4 of the JS time is spent in Ion, and most of the rest is in builtin APIs. That's all roughly what we hope for and expect.

Looking at the flame graph, it seems like the function 951339/e/this.produce is being called a lot, and it branches out to a variety of different code below that. The URL is not included in the profile, but taking my own Twitch profile I found this

transform: function(e, t) {
    var n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {};
    return Se(e, (function(e) {
        if (null === e) return null;
        var r = n.affinity,
            u = void 0 === r ? "forward" : r,
            o = e.path,
            a = e.offset;
        switch (t.type) {
            case "insert_node":
            case "move_node":
                e.path = Ke.transform(o, t, n);
                break;
            case "insert_text":
                Ke.equals(t.path, o) && (t.offset < a || t.offset === a && "forward" === u) && (e.offset += t.text.length);
                break;
            case "merge_node":
                Ke.equals(t.path, o) && (e.offset += t.position), e.path = Ke.transform(o, t, n);
                break;
            case "remove_text":
                Ke.equals(t.path, o) && t.offset <= a && (e.offset -= Math.min(a - t.offset, t.text.length));
                break;
            case "remove_node":
                if (Ke.equals(t.path, o) || Ke.isAncestor(t.path, o)) return null;
                e.path = Ke.transform(o, t, n);
                break;
            case "split_node":
                if (Ke.equals(t.path, o)) {
                    if (t.position === a && null == u) return null;
                    (t.position < a || t.position === a && "forward" === u) && (e.offset -= t.position, e.path = Ke.transform(o, t, gt(gt({}, n), {}, {
                        affinity: "forward"
                    })))
                } else e.path = Ke.transform(o, t, n)
        }
    }))
}

The name of the script implies that this is Twitch's hand-rolled wysiwyg chat input component. The distribution of execution inside transform in my profile mostly matches the attached profile, except that mine is only minimally janky even though I was holding down keys. There are a variety of things we could look into if we were trying to make this code incrementally faster (looks like they're doing a variety of operations on scripted proxies?), but it sounds like the performance hit is significant, so I'm not convinced that incremental improvements would meaningfully improve things.

It's not clear to me that Twitch's code expects to keep running for days. Have we confirmed that the same thing doesn't happen in other browsers?

Blocks: sm-js-perf
Severity: -- → N/A
Priority: -- → P3
Severity: N/A → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: