joyent/node — pull request #5850 by joelrbrandt (2013-07-14 18:28:39+00:00)
Title: stream - add firstBufferLength property to readable streams

Adds a firstBufferLength property to stream.Readable objects, as well as tests and documentation.

I really like the new stream API. However, I've run into a number of situations where my code is able to handle arbitrary amounts of data, and I want to call ```read()``` in a way that avoids unnecessary buffer copies.

One way to do this is to use objectMode. Unfortunately, many streams don't support objectMode. For example, there's no documented way of putting a Socket that comes from a ```connection``` event into objectMode.

Another way to do this is to always call ```read()``` (with no arguments) synchronously whenever a ```readable``` event occurs, and then keep a list of these buffer objects in userland code. However, this introduces two problems:
1. if the user is not actually ready to read the data, then the user has to keep these buffers around in a list. This messes up back pressure -- node will never tell the kernel that it has all the data it can handle.
2. Managing a list of buffers in userland code results in duplicating functionality that is already in ```Readable.prototype.read```

This small change introduces a getter on Readable that returns the length of the first buffer (so that a call to ```stream.read(stream.firstBufferLength)``` will read exactly the first buffer with no copies).

Using this (as opposed to ```read()``` with no arguments or ```read([some fixed length])```) can result in significant speed-ups in some cases. I created a benchmark, available here:
https://gist.github.com/joelrbrandt/5995120

This benchmark first pushes buffers sized between 10K and 20K onto a Readable stream until the stream contains 10MB of buffers (this part is not timed). Then, the buffers are read in a number of ways:

* All at once by calling ```stream.read()```
* Iteratively by calling ```stream.read(10000)```
* Iteratively by calling ```stream.read(stream.firstBufferLength)```
* Iteratively by calling ```stream.read()``` when the stream is in object mode

On my machine (Late 2010 MacBook Air with 2.13 GHz Core 2 Duo), I get the following results when running each test 400 times:

```
Read entire contents test done, read 4000000000 total bytes in 400 read calls. Total time 5.31244938 seconds
Read MIN_BLOCK_SIZE test done, read 4000000000 total bytes in 4000000 read calls. Total time 10.935569675 seconds
Read firstBufferLength test done, read 4000000000 total bytes in 2667167 read calls. Total time 0.636731486 seconds
Object mode test done, read 4000000000 total bytes in 2667747 read calls. Total time 0.464432509 seconds
```

In other words, using ```stream.read(stream.firstBufferLength)``` is 8x faster than ```read()``` and 17x faster than ```read(10000)``` under these conditions. It's also only a 1.4x slowdown over using object mode (probably due mostly to function call overhead for the getter).