Level / bench

Benchmark abstract-level databases.
MIT License
8 stars 2 forks source link

Slow results of createReadStream in the browser #21

Closed tinchoz49 closed 4 years ago

tinchoz49 commented 4 years ago

Hi @vweevers, how are you?

I found something weird using level-mem (or level too) and createReadStream in the browser.

createReadStream in the browser takes 4 seconds to read 1000 elements but for example if I read the items one by one using get the performance increase. This is only happening in the browser.

I did a repository with my test: https://github.com/tinchoz49/level-bench

Results in my machine:

Node

# level: read by get
ok ~23 ms (0 s + 22597062 ns)

# level: createReadStream
ok ~17 ms (0 s + 16784238 ns)

# level-mem: read by get
ok ~16 ms (0 s + 15990506 ns)

# level-mem: createReadStream
ok ~15 ms (0 s + 14724392 ns)

wins: level-mem: createReadStream
ok ~104 ms (0 s + 104342327 ns)

Browser

# level: read by get
ok ~286 ms (0 s + 286250001 ns)

# level: createReadStream
ok ~4.45 s (4 s + 452095000 ns)

# level-mem: read by get
ok ~51 ms (0 s + 51350000 ns)

# level-mem: createReadStream
ok ~4.47 s (4 s + 468040000 ns)

wins: level-mem: read by get
ok ~9.46 s (9 s + 463925000 ns)
vweevers commented 4 years ago

Might be due to the use of setTimeout(fn, 0) as a nexttick replacement in streams, at least for level-mem (level is a different story). And in general, browsers are slower because they have more work to do in between (unless you use a service worker) and they may throttle timers if the page gets backgrounded (in an inactive tab).

tinchoz49 commented 4 years ago

I'm going to check that.

vweevers commented 4 years ago

You might also be interested in https://github.com/Level/community/issues/70

tinchoz49 commented 4 years ago

ahh nice, I did something similar in hypercore https://github.com/hypercore-protocol/hypercore/pull/261

It really improves the performance of reading.

tinchoz49 commented 4 years ago

You right, I removed the setImmediate in the memdown iterator and the performance increased. Of course is not a soluton, just checking.

What I don't understand is that the get operation also does a setImmediate to schedule the callback read as a microtask but besides that is faster than AbstractLevelDOWN iterator.

I will keep checking, thanks!

tinchoz49 commented 4 years ago

Ok AbstractLevelDOWN iterator works great and the performance is about 100ms to read 1000 messages in the browser. So, next step is check what is doing the stream.

tinchoz49 commented 4 years ago

Thanks @vweevers it was what you said, the Stream interface doing a nextTick for the internal maybeReadMore was slowing me down and it was of how webpack implement the process.nextTick.

I try doing this to check and the performance result was really impresive:

const queueMicrotask = require('queue-microtask');

if (typeof window !== 'undefined') {
  process.nextTick = function (fn) {
    var args = new Array(arguments.length - 1)
    if (arguments.length > 1) {
      for (var i = 1; i < arguments.length; i++) {
        args[i - 1] = arguments[i]
      }
    }

    queueMicrotask(() => fn(...args))
  }
}
# level: read by get
ok ~294 ms (0 s + 294485000 ns)

# level: createReadStream
ok ~77 ms (0 s + 77445000 ns)

# level: createReadStream (-maybeToRead nextTick)
ok ~62 ms (0 s + 61809999 ns)

# level-mem: read by get
ok ~32 ms (0 s + 31644999 ns)

# level-mem: createReadStream
ok ~25 ms (0 s + 24545000 ns)

# level-mem: createReadStream (-maybeReadMore nextTick)
ok ~12 ms (0 s + 12375001 ns)

wins: level-mem: createReadStream (-maybeReadMore nextTick)
ok ~725 ms (0 s + 725215000 ns)
tinchoz49 commented 4 years ago

-maybeReadMore nextTick was an experiment to test the read stream without doing a nexttick for the maybeReadMore. Similar to what it does from2