Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplify streamToString method from node-web-streams.helper.ts #62841

Merged
merged 12 commits into from
Mar 6, 2024

Conversation

Ethan-Arrowood
Copy link
Contributor

@Ethan-Arrowood Ethan-Arrowood commented Mar 4, 2024

The encode-decode.ts file is completely replaceable with the TextDecoderStream api, but also we can simplify the streamToString function too.

Working on some benchmarks now - wanted to get CI running to see if this breaks anything though.

@ijjk
Copy link
Member

ijjk commented Mar 4, 2024

Stats from current PR

Default Build
General
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
buildDuration 14.7s 14.7s N/A
buildDurationCached 8s 6.9s N/A
nodeModulesSize 198 MB 198 MB N/A
nextStartRea..uration (ms) 447ms 439ms N/A
Client Bundles (main, webpack)
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
2453-HASH.js gzip 30.5 kB 30.5 kB N/A
3304.HASH.js gzip 181 B 181 B
3f784ff6-HASH.js gzip 53.7 kB 53.7 kB N/A
8299-HASH.js gzip 5.04 kB 5.04 kB N/A
framework-HASH.js gzip 45.2 kB 45.2 kB
main-app-HASH.js gzip 243 B 243 B
main-HASH.js gzip 32.1 kB 32.2 kB N/A
webpack-HASH.js gzip 1.68 kB 1.68 kB N/A
Overall change 45.6 kB 45.6 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
_app-HASH.js gzip 196 B 197 B N/A
_error-HASH.js gzip 184 B 184 B
amp-HASH.js gzip 505 B 505 B
css-HASH.js gzip 324 B 325 B N/A
dynamic-HASH.js gzip 2.5 kB 2.5 kB N/A
edge-ssr-HASH.js gzip 258 B 258 B
head-HASH.js gzip 352 B 352 B
hooks-HASH.js gzip 370 B 371 B N/A
image-HASH.js gzip 4.2 kB 4.2 kB
index-HASH.js gzip 259 B 259 B
link-HASH.js gzip 2.67 kB 2.67 kB N/A
routerDirect..HASH.js gzip 314 B 312 B N/A
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 309 B 309 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 6.56 kB 6.56 kB
Client Build Manifests
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
_buildManifest.js gzip 483 B 485 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
index.html gzip 528 B 529 B N/A
link.html gzip 542 B 541 B N/A
withRouter.html gzip 524 B 523 B N/A
Overall change 0 B 0 B
Edge SSR bundle Size
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
edge-ssr.js gzip 95 kB 95 kB N/A
page.js gzip 3.06 kB 3.07 kB N/A
Overall change 0 B 0 B
Middleware size
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
middleware-b..fest.js gzip 624 B 623 B N/A
middleware-r..fest.js gzip 151 B 151 B
middleware.js gzip 25.5 kB 25.5 kB N/A
edge-runtime..pack.js gzip 839 B 839 B
Overall change 990 B 990 B
Next Runtimes
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
app-page-exp...dev.js gzip 171 kB 171 kB N/A
app-page-exp..prod.js gzip 96.7 kB 96.7 kB N/A
app-page-tur..prod.js gzip 98.5 kB 98.5 kB N/A
app-page-tur..prod.js gzip 92.9 kB 92.9 kB N/A
app-page.run...dev.js gzip 149 kB 149 kB N/A
app-page.run..prod.js gzip 91.4 kB 91.4 kB N/A
app-route-ex...dev.js gzip 21.3 kB 21.3 kB
app-route-ex..prod.js gzip 15 kB 15 kB
app-route-tu..prod.js gzip 15 kB 15 kB
app-route-tu..prod.js gzip 14.8 kB 14.8 kB
app-route.ru...dev.js gzip 21 kB 21 kB
app-route.ru..prod.js gzip 14.8 kB 14.8 kB
pages-api-tu..prod.js gzip 9.54 kB 9.54 kB
pages-api.ru...dev.js gzip 9.81 kB 9.81 kB
pages-api.ru..prod.js gzip 9.54 kB 9.54 kB
pages-turbo...prod.js gzip 22.3 kB 22.3 kB N/A
pages.runtim...dev.js gzip 23 kB 23 kB N/A
pages.runtim..prod.js gzip 22.3 kB 22.3 kB N/A
server.runti..prod.js gzip 50.6 kB 50.6 kB N/A
Overall change 131 kB 131 kB
build cache
vercel/next.js canary vercel/next.js simplify-stream-to-string Change
0.pack gzip 1.56 MB 1.56 MB N/A
index.pack gzip 106 kB 105 kB N/A
Overall change 0 B 0 B
Diff details
Diff for middleware.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for app-page-exp..ntime.dev.js

Diff too large to display

Diff for app-page-exp..time.prod.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page-tur..time.prod.js

Diff too large to display

Diff for app-page.runtime.dev.js
failed to diff
Diff for app-page.runtime.prod.js

Diff too large to display

Diff for pages-turbo...time.prod.js

Diff too large to display

Diff for pages.runtime.dev.js

Diff too large to display

Diff for pages.runtime.prod.js

Diff too large to display

Diff for server.runtime.prod.js

Diff too large to display

Commit: d483134

@Ethan-Arrowood
Copy link
Contributor Author

I'm seeing about a 50% increase 😄

results:

❯ node packages/next/src/server/stream-utils/bench.js
[
  [
    0.550416998565197,
    0.6964169964194298,
    0.5036659985780716,
    0.8374169990420341,
    0.4388749971985817
  ],
  [
    0.2948329970240593,
    0.34987500309944153,
    0.23741699755191803,
    0.44016599655151367,
    0.22670800238847733
  ]
]
avg time of old =  0.6053583979606628
avg time of new =  0.309799799323082

bench.js:

const assert = require('assert')
const fs = require('fs');
const stream = require('stream');
const path = require('path');

function createDecodeTransformStream(decoder = new TextDecoder()) {
    return new TransformStream({
      transform(chunk, controller) {
        return controller.enqueue(decoder.decode(chunk, { stream: true }))
      },
      flush(controller) {
        return controller.enqueue(decoder.decode())
      },
    })
  }

async function oldStreamToString (stream) {
    let buffer = ''

    await stream
      // Decode the streamed chunks to turn them into strings.
      .pipeThrough(createDecodeTransformStream())
      .pipeTo(
        new WritableStream({
          write(chunk) {
            buffer += chunk
          },
        })
      )
  
    return buffer
}

async function newStreamToString (stream) {
    const decoder = new TextDecoder()
    let string = ''
  
    for await (const chunk of stream) {
      string += decoder.decode(chunk, { stream: true })
    }
  
    string += decoder.decode()
  
    return string
}

async function benchOld (p, exp) {
    const r = fs.createReadStream(p);
    const rs = stream.Readable.toWeb(r);

    const s = performance.now();
    const act = await oldStreamToString(rs);
    const e = performance.now();

    assert.strictEqual(act, exp);

    return e - s;
}

async function benchNew (p, exp) {
    const r = fs.createReadStream(p);
    const rs = stream.Readable.toWeb(r);

    const s = performance.now();
    const act = await newStreamToString(rs);
    const e = performance.now();

    assert.strictEqual(act, exp);

    return e - s;
}

function avg (l) {
    let t = 0;
    for (const i of l) {
        t += i;
    }
    return t / l.length;
}

async function bench () {
    const p = path.join(__dirname, './input.txt');
    const exp = fs.readFileSync(p, 'utf-8');

    // warmup;

    await benchOld(p, exp);
    await benchNew(p, exp);

    const results = [[], []]; // old, new
    for (let i = 0; i < 5; i++) {
        const o = await benchOld(p, exp);
        const n = await benchNew(p, exp);

        results[0].push(o);
        results[1].push(n);
    }

    console.log(results);
    console.log('avg time of old = ', avg(results[0]))
    console.log('avg time of new = ', avg(results[1]))
}

bench();

@ijjk
Copy link
Member

ijjk commented Mar 4, 2024

Failing test suites

Commit: d483134

pnpm test test/integration/app-dir-export/test/dynamicpage-prod.test.ts

  • app dir - with output export - dynamic api route prod > production mode > should work in prod with dynamicPage 'force-static'
Expand output

● app dir - with output export - dynamic api route prod › production mode › should work in prod with dynamicPage 'force-static'

thrown: "Exceeded timeout of 60000 ms for a test.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  12 |           'Page with `dynamic = "force-dynamic"` couldn\'t be exported. `output: "export"` requires all pages be renderable statically',
  13 |       },
> 14 |     ])(
     |      ^
  15 |       'should work in prod with dynamicPage $dynamicPage',
  16 |       async ({ dynamicPage, expectedErrMsg }) => {
  17 |         await runTests({ isDev: false, dynamicPage, expectedErrMsg })

  at ../node_modules/.pnpm/jest-each@29.7.0/node_modules/jest-each/build/bind.js:47:15
      at Array.forEach (<anonymous>)
  at integration/app-dir-export/test/dynamicpage-prod.test.ts:14:6
  at integration/app-dir-export/test/dynamicpage-prod.test.ts:4:52
  at Object.describe (integration/app-dir-export/test/dynamicpage-prod.test.ts:3:1)

Read more about building and testing Next.js in contributing.md.

@Ethan-Arrowood Ethan-Arrowood enabled auto-merge (squash) March 5, 2024 19:31
string += decoder.decode(chunk, { stream: true })
}

string += decoder.decode()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a last decode call needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://nodejs.org/api/util.html#textdecoderdecodeinput-options

Decodes the input and returns a string. If options.stream is true, any incomplete byte sequences occurring at the end of the input are buffered internally and emitted after the next call to textDecoder.decode().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially, this makes sure everything is decoded, even if there are incomplete bytes.

@Ethan-Arrowood Ethan-Arrowood merged commit 3c75ae7 into canary Mar 6, 2024
67 checks passed
@Ethan-Arrowood Ethan-Arrowood deleted the simplify-stream-to-string branch March 6, 2024 18:15
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants