镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Faster Transform stream #33397

Description

@ronag

Some thoughts on how to make Transform streams faster.

Part of the overhead of Transform (and PassThrough) is that it is actually 2 streams, one Writable and one Readable, both with buffering and state management, which are connected together.

We could try to skip this and implement Transform as a Readable which implements the Writable interface and proxies the naming.

e.g.

class FastTransform extends Readable {
  constructor(options) {
    super(options)
    this._writableState = {
      length: 0,
      needDrain: false,
      ended: false,
      finished: false
    }
  }
  get writableEnded () {
    return this._writableState.ended
  }
  get writableFinished () {
    return this._writableState.finished
  }
  _read () {
    const rState = this._readableState
    const wState = this._writableState

    if (!wState.needDrain) {
      return
    }

    if (wState.length + rState.length > rState.highWaterMark) {
      return
    }

    wState.needDrain = false
    this.emit('drain')
  }
  write (chunk) {
    const rState = this._readableState
    const wState = this._writableState

    const len = chunk.length

    wState.length += len
    this._transform(chunk, null, (err, data) => {
      wState.length -= len
      if (err) {
        this.destroy(err)
      } else if (data != null) {
        this.push(data)
      }
      this._read()
    })

    wState.needDrain = wState.length + rState.length > rState.highWaterMark

    return wState.needDrain
  }
  end () {
    const wState = this._writableState

    wState.ended = true
    if (this._flush) {
      this._flush(chunk, (err, data) => {
        const wState = this._writableState
        if (err) {
          this.destroy(err)
        } else {
          if (data != null) {
            this.push(data)
          }
          this.push(null)
          wState.finished = true
          this.emit('finish')
        }
      })
    } else {
      this.push(null)
      wState.finished = true
      this.emit('finish')
    }
  }
}

// TODO: Make Writable[Symbol.hasInstance] recognize `FastTransform`.

Making this fully backwards compatible with Transform might be difficult.

Activity

  1. ronag commented on May 14, 2020

    @ronag
    MemberAuthor

    Thoughts @nodejs/streams?

  2. mcollina commented on May 14, 2020

    @mcollina
    SponsorMember

    How much will we gain from this? I'm +1, possibly with a better name.

  3. ronag commented on May 14, 2020

    @ronag
    MemberAuthor

    @mcollina: ~60%

    $ time node fast-transform.js 
    
    real    0m1.582s
    user    0m1.737s
    sys     0m0.157s
    
    $ time node transform.js 
    
    real    0m2.671s
    user    0m2.683s
    sys     0m0.069s
    undici$ 

    possibly with a better name.

    FasterTransform? 😄

  4. self-assigned this
    on May 14, 2020
  5. added
    streamIssues and PRs related to Node.js streams.
    on May 14, 2020
  6. targos commented on May 14, 2020

    @targos
    Member

    How does it compare to transforming with an async generator function?

  7. ronag commented on May 14, 2020

    @ronag
    MemberAuthor
    $ time node async-generator.js 
    real    0m0.788s
    user    0m0.772s
    sys     0m0.075s
    
    $ time node fast-transform.js 
    real    0m1.612s
    user    0m1.729s
    sys     0m0.169s
    

    So using an async generator in pipeline might be significantly faster. Is it worth further optimizing Transform? Note that an optimization as suggested here does make it harder to maintain than today's rather trivial implementation.

  8. ronag commented on May 17, 2020

    @ronag
    MemberAuthor

    pipeline will convert a Readable source into an async iterator and read from it that way. Might introduce unnecessary overhead. Will investigate that first.

  9. clshortfuse commented on Jun 16, 2020

    @clshortfuse
    Contributor

    Apparently I should be using pipeline. This does seem like a good replacement for PassThrough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

streamIssues and PRs related to Node.js streams.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions