API reference docs page is very veeery slow

I’m talking about this:
https://platform.openai.com/docs/api-reference/introduction

I do like that it is kept in one single page, very llm friendly. But scrolling throught it lowers the fps to 10, or at least it feels like it.

Scrolling through static text should definitely not have this performance impact.

1 Like

Oh yes. The documentation pages are brutally inefficient

The red indicates jank, which is “sluggishness in a user interface, usually caused by executing long tasks on the main thread, blocking rendering, or expending too much processor power on background processes.”

The yellow graph beneath the red indicates how much CPU was used (near-constant 100%)

VS Gemini Docs

The main issue here is that OpenAI uses hydration to fill in the documentation (which requires heavy javascript usage) while other documentation providers use static site generation.

6 Likes

It seems to be worse on Firefox? But yeah, it’s a bit clunky… trying to do a lot…

3 Likes

Good catch. Yes on Firefox it seems consistently worse. On Chromium it strangely presents itself differently by causing a lock-up each time a shift of category happens but then comes down to better usage. This is a result of me scrolling somewhat fast through the page. Each jump being when the category changes.

3 Likes

I’m gonna have nightmares tonight after looking at that lol… thanks! :wink:

I can’t imagine the team responsible for keeping that updates all the time. I don’t think static HTML would even work well?

That said, there has to be a better way… Likely coming down to priorities, though. Easy to forget OpenAI is one of the fastest growing companies of all time (I think?)

1 Like

Of course it would. The page is already loaded. The main issue here is that they (OpenAI) are using hydration to programmatically fill in the information just in time, causing the main thread to block and therefore cause jank.

If it’s static there’s no additional content to load and programmatically render

Try and turn off Javascript and visit pages of API documentation:

vs

It’s really not the end of the world. Or a big issue. It’s just something that is comparatively slow to other typical API documentation pages.

2 Likes

I just meant the docs change so often it would be harder to keep it updated that way?

Concur! :wink:

1 Like

I think there’s some confusion with the word “static”. Of course the documents can be updated. The difference here is that the provider compiles whatever the raw material is into something easily digestible by the client (HTML/CSS/JSS) and passes it directly to the client.

Instead of “dynamic”, where a barebone structure is delivered and programmatically filled using javascript using usually a technique like hydration. Simply put, dynamic means it can dynamically adjust via javascript.

For example some of my content is written in HUGO. I can edit a document on-the-fly, and HUGO will re-compile it to HTML/CSS/JS and then automatically update my server. This is great for blog posting and having uber-fast load times with very little javascript overhead. It also means I have a website that doesn’t depend on javascript.

Updating the documentation has nothing to do with it being static or dynamic. In both cases, as long as it’s setup correctly one can have their documentation written in a single source (such as markdown) and have it reflected “live” to users.

I would use dynamic if for example I was using ChatGPT, because there’s a lot of things moving around, things being updated, etc etc.

For documentation though. It’s mostly a “static” page.

3 Likes

Why have a mere document site when they could be using API keys and tons of scripts to load up an interface and data to manage your project or see assistants threads at the same time?

Just a few undesirables to drop in your uBlock Origin personal filters list.

featureassets.org/v1/initialize
widget.intercom.io
browser-intake-datadoghq.com
prodregistryv2.org
s.gravatar.com
||chatgpt.com/ces/v1/
chatgpt.com/backend-api/o11y/v1/traces
chatgpt.com/backend-api/user_surveys/active
chatgpt.com/backend-api/me
chatgpt.com/sentinel
chatgpt.com/backend-api/sentinel
platform.openai.com/cdn-cgi/challenge-platform/
chatgpt.com/cdn-cgi/challenge-platform/scripts

Then there’s more, without even going after the current script names; I got a held-out half dozen to where it can’t even tell if a web browser is the ChatGPT app…

However, it is the javascript render time chewing away when you are waiting to see “chat” documentation.

You could also block 200kB of fonts from cdn.openai.com/common/fonts/

Guess what two tabs of document site chomps 500x the RAM as the size the entire API specification the page is built on…

1 Like

It’s not hydration @RonaldGRuckus, just bad react programming. If it was hydration I could scroll through a single endpoint without lag, as the content would be already hydrated.

There’s no react scan extension yet, so I don’t know whether the following is true, but I think it’s just that each time I scroll a bit the whole page tries to react rerender the virtual dom. It doesn’t happen with small pages, but for the whole api reference this is fine.

The new react compiler should fix this, but it looks like some useMemo calls in the right place would fix this issue, which, well openai team, if you are reading this, I would very much appreciate it :smiley:

Edit: I note that I have no idea how do this react app really works, I’m just hypothesizing and I could be wrong. I just see the react devtools icon pop up in this page and my js biases flare up.

1 Like

Hydration doesn’t necessarily involve rendering the full page in one moment. It’s most definitely hydrating whatever section is currently being viewed, causing a massive jump in CPU usage each time the section changes.

You can actually see it happen live, as the page starts as an empty shell:

And then starts to hydrate with content

I imagine the reason they did this, as @PaulBellow has mentioned, is simplicity. They use the same framework for their documentation as they do with their platform, and ChatGPT itself (I believe)

Digging a little deeper I believe what OpenAI is using is ReactJS “Concurrent Rendering”

I think initially the way it was all setup was fine, but they have added so much stuff and, yeah, optimizing documentation probably isn’t the highest on their priorities :rofl: probably waiting until ChatGPT can do it for them

1 Like

I mean, not that much related to the topic at hand, but if you:

  • open the api reference
  • wait until all content is loaded (hydration happening here)
  • scroll ← here gets slow.

open the dev tools, you’ll see that the only requests done are for tracking. All the content is loaded already (which is fine btw, that isn’t that much content). It’s not hydration the problem, the problem is that when I scroll something happens, probably all components in the page rerender, which is fine for really small pages, but for large ones it is awful.

You don’t even need to see the network requests. If you wait until the page is loaded you’ll see that all the html for the docs is there already. This makes it easy to ctrl + a and paste it into an llm, which is very nice!

But it definitely shouldn’t be that slow. It’s just static text.

Yes. It seems to be a rendering issue. I think I have mis-used the term “hydration”, my apologies.

Take note of the function K being the initialization of the jank.

If you inspect the function K you will find the following:

function K() {
        if (I !== null) {
            var H = e.unstable_now();
            $ = H;
            var F = !0;
            try {
                F = I(!0, H)
            } finally {
                F ? U() : (O = !1,
                I = null)
            }
        } else
            O = !1
    }

Minified, but thankfully ChatGPT is very good at understanding it.

It will tell you

It looks like part of a scheduling system, possibly inspired by or part of React’s Scheduler or a similar asynchronous task-handling mechanism.

Finally, getting down to the function HV starts to demonstrate actual rendering happening.

function HV(e, t) {
    if (Dy = -1,
    $y = 0,
    ct & 6)
        throw Error(le(327));
    var n = e.callbackNode;
    if (Qf() && e.callbackNode !== n)
        return null;
    var r = U2(e, e === er ? dr : 0);
    if (r === 0)
        return null;
    if (r & 30 || r & e.expiredLanes || t)
        t = i_(e, r);
    else {
        t = r;
        var i = ct;
        ct |= 2;
        var o = zV();
        (er !== e || dr !== t) && (cs = null,
        kp = xn() + 500,
        $l(e, t));
        do
            try {
                p_e();
                break
            } catch (s) {
                VV(e, s)
            }
        while (!0);
        M7(),
        t_.current = o,
        ct = i,
        Nn !== null ? t = 0 : (er = null,
        dr = 0,
        t = Bn)
    }
    if (t !== 0) {
        if (t === 2 && (i = C5(e),
        i !== 0 && (r = i,
        t = Q5(e, i))),
        t === 1)
            throw n = V0,
            $l(e, 0),
            Ou(e, r),
            vi(e, xn()),
            n;
        if (t === 6)
            Ou(e, r);
        else {
            if (i = e.current.alternate,
            !(r & 30) && !d_e(i) && (t = i_(e, r),
            t === 2 && (o = C5(e),
            o !== 0 && (r = o,
            t = Q5(e, o))),
            t === 1))
                throw n = V0,
                $l(e, 0),
                Ou(e, r),
                vi(e, xn()),
                n;
            switch (e.finishedWork = i,
            e.finishedLanes = r,
            t) {
            case 0:
            case 1:
                throw Error(le(345));
            case 2:
                nl(e, ui, cs);
                break;
            case 3:
                if (Ou(e, r),
                (r & 130023424) === r && (t = Z7 + 500 - xn(),
                10 < t)) {
                    if (U2(e, 0) !== 0)
                        break;
                    if (i = e.suspendedLanes,
                    (i & r) !== r) {
                        Br(),
                        e.pingedLanes |= e.suspendedLanes & i;
                        break
                    }
                    e.timeoutHandle = M5(nl.bind(null, e, ui, cs), t);
                    break
                }
                nl(e, ui, cs);
                break;
            case 4:
                if (Ou(e, r),
                (r & 4194240) === r)
                    break;
                for (t = e.eventTimes,
                i = -1; 0 < r; ) {
                    var a = 31 - zo(r);
                    o = 1 << a,
                    a = t[a],
                    a > i && (i = a),
                    r &= ~o
                }
                if (r = i,
                r = xn() - r,
                r = (120 > r ? 120 : 480 > r ? 480 : 1080 > r ? 1080 : 1920 > r ? 1920 : 3e3 > r ? 3e3 : 4320 > r ? 4320 : 1960 * l_e(r / 1960)) - r,
                10 < r) {
                    e.timeoutHandle = M5(nl.bind(null, e, ui, cs), r);
                    break
                }
                nl(e, ui, cs);
                break;
            case 5:
                nl(e, ui, cs);
                break;
            default:
                throw Error(le(329))
            }
        }
    }
    return vi(e, xn()),
    e.callbackNode === n ? HV.bind(null, e) : null
}

And, then, the actual rendering begins to happen under “updateFiberRecursively”

Which is a major contributor to the jank

Nah don’t worry, I got confused also with hydration at first. Webdev is a bit convoluted.

I don’t even think it is a react rendering issue. Everything is already rendered, you can see by just ctrl + f this string, “gpt-3.5-turbo-1106”. Many instances found.

It’s just this cute on scroll - change page behaviour that’s driving me mad.

You can see it in the inspector HTML but the actual rendering is not available, it’s not “hydrated” either. I think this is interplay between the browser and ReactJS.

This is why I believe it’s a result of the hydration. ReactJS is selectively deciding what to work or “hydrate” or “render”, based on what the user is currently looking at.

So when you are scrolling the browser is now visually loading in the content, and ReactJS on top of it is hydrating the content to give it life. This would cause the functions to trigger, and lead to jank if the functions become too burdensome.

I don’t think this is as simple as memoization, but more of using the wrong tools for the job (of delivering what should be mostly static content)

Nah man, react isn’t doing that, or else you wouldn’t see many results when searching for that string. The content is already there, and visible, or else your browser shouldn’t be able to find it using ctrl + f. You are getting stuck into the hydration part, which has nothing to do with the real issue.

Look at this:

(I did wait for the page to load, hit record button to the performance devtool tab, and scroll for a bit)

You can see the scroll event taking 131ms. That code will run every time you scroll, each scroll event taking 100ms average to trigger. The function call that blocks the main ui thread is this querySelector.

Basically, each time you scroll, the page does many querySelector calls for each heading to update the url. Which is fine for a small page, but scales horribly.

Well I might be wrong, after doing some more profiling there are other parts that might be also slow, not just the query selector (which is also slow). I’m sure it’s not rendering, at least not html rendering, my devtools profiler shows that most of the time is spent scripting. So maybe react rerenders.

idk the page is terrible either way. The way it’s updating the url doesn’t look like proper react, you shouldn’t be query selectoring the whole page to change the url Jesus, that’s horrible react code.

I don’t get these 100ms querySelector blocks.

I have tried in both Firefox and Chromium and both indicate that the jank is directly coming from ReactJS rendering.

Notice that in all my screenshots there is a Parent K function that initializes everything.

You can go further and notice that K is called rapidly as the scrolling happens and ReactJS is rapidly rendering the elements

The K function is directly correlated with the jank. So, I agree that scrolling is the reason why it happens. I just am led to believe that this is caused by the rendering.

You can actually see it happen live if you scroll rapidly, the page will be momentarily empty. This happens on all OpenAI pages I believe, including ChatGPT.

P.S. This has been fun to investigate, wish I could give it more time. Would be fun to find the absolute reason. I’m stuck on the rendering because of how I see the page “pop-up” and how the functions taking the most time are related to ReactJS rendering

1 Like

Ah man it’s been a time suck for me aswell, I have a demo tomorrow that I might have to delay because of putting time here.

I guess that’s the real performance problem haha, us getting captivated by this.

On how to get the querySelector calls, I started recording the profiler and then scrolled a bit. I get inconsistent results, sometimes the querySelector calls apppear much less, and more the calls that you are observing.

It’s quite interesting I agree, chrome devtools are so good.

2 Likes