| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e89a49a commit cdcefd7
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,32 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common.js'); | ||
| 4 | + | ||
| 5 | + const bench = common.createBenchmark(main, { | ||
| 6 | + consumers: [2, 8, 32], | ||
| 7 | + batches: [1e4], | ||
| 8 | + backpressure: ['block'], | ||
| 9 | + n: [5], | ||
| 10 | + }, { | ||
| 11 | + flags: ['--experimental-stream-iter'], | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + async function main({ consumers, batches, backpressure, n }) { | ||
| 15 | + const { share, array } = require('stream/iter'); | ||
| 16 | + const chunk = Buffer.alloc(1024); | ||
| 17 | + const totalOps = batches * consumers * n; | ||
| 18 | + | ||
| 19 | + async function* source() { | ||
| 20 | + for (let i = 0; i < batches; i++) { | ||
| 21 | + yield [chunk]; | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + bench.start(); | ||
| 26 | + for (let i = 0; i < n; i++) { | ||
| 27 | + const shared = share(source(), { highWaterMark: 64, backpressure }); | ||
| 28 | + const readers = Array.from({ length: consumers }, () => array(shared.pull())); | ||
| 29 | + await Promise.all(readers); | ||
| 30 | + } | ||
| 31 | + bench.end(totalOps); | ||
| 32 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -343,8 +343,9 @@ class BroadcastImpl { | |||
| 343 | 343 | // Private methods | |
| 344 | 344 | ||
| 345 | 345 | #recomputeMinCursor() { | |
| 346 | - this.#cachedMinCursor = getMinCursor( | ||
| 346 | + const { minCursor } = getMinCursor( | ||
| 347 | 347 | this.#consumers, this.#bufferStart + this.#buffer.length); | |
| 348 | + this.#cachedMinCursor = minCursor; | ||
| 348 | 349 | this.#minCursorDirty = false; | |
| 349 | 350 | } | |
| 350 | 351 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,8 @@ class ShareImpl { | |||
| 77 | 77 | #cancelled = false; | |
| 78 | 78 | #pulling = false; | |
| 79 | 79 | #pullWaiters = []; | |
| 80 | + #cachedMinCursor = 0; | ||
| 81 | + #cachedMinCursorConsumers = 0; | ||
| 80 | 82 | ||
| 81 | 83 | constructor(source, options) { | |
| 82 | 84 | this.#source = source; | |
@@ -114,6 +116,14 @@ class ShareImpl { | |||
| 114 | 116 | }; | |
| 115 | 117 | ||
| 116 | 118 | this.#consumers.add(state); | |
| 119 | + if (this.#consumers.size === 1) { | ||
| 120 | + this.#cachedMinCursor = state.cursor; | ||
| 121 | + this.#cachedMinCursorConsumers = 1; | ||
| 122 | + } else if (state.cursor === this.#cachedMinCursor) { | ||
| 123 | + this.#cachedMinCursorConsumers++; | ||
| 124 | + } else { | ||
| 125 | + this.#recomputeMinCursor(); | ||
| 126 | + } | ||
| 117 | 127 | const self = this; | |
| 118 | 128 | ||
| 119 | 129 | return { | |
@@ -139,22 +149,26 @@ class ShareImpl { | |||
| 139 | 149 | ||
| 140 | 150 | if (self.#cancelled) { | |
| 141 | 151 | state.detached = true; | |
| 142 | - self.#consumers.delete(state); | ||
| 152 | + self.#deleteConsumer(state); | ||
| 143 | 153 | return { __proto__: null, done: true, value: undefined }; | |
| 144 | 154 | } | |
| 145 | 155 | ||
| 146 | 156 | // Check if data is available in buffer | |
| 147 | 157 | const bufferIndex = state.cursor - self.#bufferStart; | |
| 148 | 158 | if (bufferIndex < self.#buffer.length) { | |
| 149 | 159 | const chunk = self.#buffer.get(bufferIndex); | |
| 160 | + const cursor = state.cursor; | ||
| 150 | 161 | state.cursor++; | |
| 151 | - self.#tryTrimBuffer(); | ||
| 162 | + if (cursor === self.#cachedMinCursor && | ||
| 163 | + --self.#cachedMinCursorConsumers === 0) { | ||
| 164 | + self.#tryTrimBuffer(); | ||
| 165 | + } | ||
| 152 | 166 | return { __proto__: null, done: false, value: chunk }; | |
| 153 | 167 | } | |
| 154 | 168 | ||
| 155 | 169 | if (self.#sourceExhausted) { | |
| 156 | 170 | state.detached = true; | |
| 157 | - self.#consumers.delete(state); | ||
| 171 | + self.#deleteConsumer(state); | ||
| 158 | 172 | if (self.#sourceError) throw self.#sourceError; | |
| 159 | 173 | return { __proto__: null, done: true, value: undefined }; | |
| 160 | 174 | } | |
@@ -163,7 +177,7 @@ class ShareImpl { | |||
| 163 | 177 | const canPull = await self.#waitForBufferSpace(); | |
| 164 | 178 | if (!canPull) { | |
| 165 | 179 | state.detached = true; | |
| 166 | - self.#consumers.delete(state); | ||
| 180 | + self.#deleteConsumer(state); | ||
| 167 | 181 | if (self.#sourceError) throw self.#sourceError; | |
| 168 | 182 | return { __proto__: null, done: true, value: undefined }; | |
| 169 | 183 | } | |
@@ -176,17 +190,19 @@ class ShareImpl { | |||
| 176 | 190 | state.detached = true; | |
| 177 | 191 | state.resolve = null; | |
| 178 | 192 | state.reject = null; | |
| 179 | - self.#consumers.delete(state); | ||
| 180 | - self.#tryTrimBuffer(); | ||
| 193 | + if (self.#deleteConsumer(state)) { | ||
| 194 | + self.#tryTrimBuffer(); | ||
| 195 | + } | ||
| 181 | 196 | return { __proto__: null, done: true, value: undefined }; | |
| 182 | 197 | }, | |
| 183 | 198 | ||
| 184 | 199 | async throw() { | |
| 185 | 200 | state.detached = true; | |
| 186 | 201 | state.resolve = null; | |
| 187 | 202 | state.reject = null; | |
| 188 | - self.#consumers.delete(state); | ||
| 189 | - self.#tryTrimBuffer(); | ||
| 203 | + if (self.#deleteConsumer(state)) { | ||
| 204 | + self.#tryTrimBuffer(); | ||
| 205 | + } | ||
| 190 | 206 | return { __proto__: null, done: true, value: undefined }; | |
| 191 | 207 | }, | |
| 192 | 208 | }; | |
@@ -254,9 +270,11 @@ class ShareImpl { | |||
| 254 | 270 | this.#bufferStart++; | |
| 255 | 271 | for (const consumer of this.#consumers) { | |
| 256 | 272 | if (consumer.cursor < this.#bufferStart) { | |
| 273 | + this.#deleteConsumerFromMin(consumer); | ||
| 257 | 274 | consumer.cursor = this.#bufferStart; | |
| 258 | 275 | } | |
| 259 | 276 | } | |
| 277 | + this.#recomputeMinCursor(); | ||
| 260 | 278 | return true; | |
| 261 | 279 | case 'drop-newest': | |
| 262 | 280 | return true; | |
@@ -324,18 +342,41 @@ class ShareImpl { | |||
| 324 | 342 | } | |
| 325 | 343 | ||
| 326 | 344 | #tryTrimBuffer() { | |
| 327 | - const minCursor = getMinCursor( | ||
| 328 | - this.#consumers, this.#bufferStart + this.#buffer.length); | ||
| 329 | - const trimCount = minCursor - this.#bufferStart; | ||
| 345 | + if (this.#cachedMinCursorConsumers === 0) { | ||
| 346 | + this.#recomputeMinCursor(); | ||
| 347 | + } | ||
| 348 | + const trimCount = this.#cachedMinCursor - this.#bufferStart; | ||
| 330 | 349 | if (trimCount > 0) { | |
| 331 | 350 | this.#buffer.trimFront(trimCount); | |
| 332 | - this.#bufferStart = minCursor; | ||
| 351 | + this.#bufferStart = this.#cachedMinCursor; | ||
| 333 | 352 | for (let i = 0; i < this.#pullWaiters.length; i++) { | |
| 334 | 353 | this.#pullWaiters[i](); | |
| 335 | 354 | } | |
| 336 | 355 | this.#pullWaiters = []; | |
| 337 | 356 | } | |
| 338 | 357 | } | |
| 358 | + | ||
| 359 | + #recomputeMinCursor() { | ||
| 360 | + const { minCursor, minCursorConsumers } = getMinCursor( | ||
| 361 | + this.#consumers, this.#bufferStart + this.#buffer.length); | ||
| 362 | + this.#cachedMinCursor = minCursor; | ||
| 363 | + this.#cachedMinCursorConsumers = minCursorConsumers; | ||
| 364 | + } | ||
| 365 | + | ||
| 366 | + #deleteConsumerFromMin(consumer) { | ||
| 367 | + if (consumer.cursor === this.#cachedMinCursor) { | ||
| 368 | + this.#cachedMinCursorConsumers--; | ||
| 369 | + return this.#cachedMinCursorConsumers === 0; | ||
| 370 | + } | ||
| 371 | + return false; | ||
| 372 | + } | ||
| 373 | + | ||
| 374 | + #deleteConsumer(consumer) { | ||
| 375 | + if (this.#consumers.delete(consumer)) { | ||
| 376 | + return this.#deleteConsumerFromMin(consumer); | ||
| 377 | + } | ||
| 378 | + return false; | ||
| 379 | + } | ||
| 339 | 380 | } | |
| 340 | 381 | ||
| 341 | 382 | // ============================================================================= | |
@@ -352,6 +393,8 @@ class SyncShareImpl { | |||
| 352 | 393 | #sourceExhausted = false; | |
| 353 | 394 | #sourceError = null; | |
| 354 | 395 | #cancelled = false; | |
| 396 | + #cachedMinCursor = 0; | ||
| 397 | + #cachedMinCursorConsumers = 0; | ||
| 355 | 398 | ||
| 356 | 399 | constructor(source, options) { | |
| 357 | 400 | this.#source = source; | |
@@ -383,6 +426,14 @@ class SyncShareImpl { | |||
| 383 | 426 | }; | |
| 384 | 427 | ||
| 385 | 428 | this.#consumers.add(state); | |
| 429 | + if (this.#consumers.size === 1) { | ||
| 430 | + this.#cachedMinCursor = state.cursor; | ||
| 431 | + this.#cachedMinCursorConsumers = 1; | ||
| 432 | + } else if (state.cursor === this.#cachedMinCursor) { | ||
| 433 | + this.#cachedMinCursorConsumers++; | ||
| 434 | + } else { | ||
| 435 | + this.#recomputeMinCursor(); | ||
| 436 | + } | ||
| 386 | 437 | const self = this; | |
| 387 | 438 | ||
| 388 | 439 | return { | |
@@ -396,26 +447,30 @@ class SyncShareImpl { | |||
| 396 | 447 | } | |
| 397 | 448 | if (self.#sourceError) { | |
| 398 | 449 | state.detached = true; | |
| 399 | - self.#consumers.delete(state); | ||
| 450 | + self.#deleteConsumer(state); | ||
| 400 | 451 | throw self.#sourceError; | |
| 401 | 452 | } | |
| 402 | 453 | if (self.#cancelled) { | |
| 403 | 454 | state.detached = true; | |
| 404 | - self.#consumers.delete(state); | ||
| 455 | + self.#deleteConsumer(state); | ||
| 405 | 456 | return { __proto__: null, done: true, value: undefined }; | |
| 406 | 457 | } | |
| 407 | 458 | ||
| 408 | 459 | const bufferIndex = state.cursor - self.#bufferStart; | |
| 409 | 460 | if (bufferIndex < self.#buffer.length) { | |
| 410 | 461 | const chunk = self.#buffer.get(bufferIndex); | |
| 462 | + const cursor = state.cursor; | ||
| 411 | 463 | state.cursor++; | |
| 412 | - self.#tryTrimBuffer(); | ||
| 464 | + if (cursor === self.#cachedMinCursor && | ||
| 465 | + --self.#cachedMinCursorConsumers === 0) { | ||
| 466 | + self.#tryTrimBuffer(); | ||
| 467 | + } | ||
| 413 | 468 | return { __proto__: null, done: false, value: chunk }; | |
| 414 | 469 | } | |
| 415 | 470 | ||
| 416 | 471 | if (self.#sourceExhausted) { | |
| 417 | 472 | state.detached = true; | |
| 418 | - self.#consumers.delete(state); | ||
| 473 | + self.#deleteConsumer(state); | ||
| 419 | 474 | return { __proto__: null, done: true, value: undefined }; | |
| 420 | 475 | } | |
| 421 | 476 | ||
@@ -436,13 +491,15 @@ class SyncShareImpl { | |||
| 436 | 491 | self.#bufferStart++; | |
| 437 | 492 | for (const consumer of self.#consumers) { | |
| 438 | 493 | if (consumer.cursor < self.#bufferStart) { | |
| 494 | + self.#deleteConsumerFromMin(consumer); | ||
| 439 | 495 | consumer.cursor = self.#bufferStart; | |
| 440 | 496 | } | |
| 441 | 497 | } | |
| 498 | + self.#recomputeMinCursor(); | ||
| 442 | 499 | break; | |
| 443 | 500 | case 'drop-newest': | |
| 444 | 501 | state.detached = true; | |
| 445 | - self.#consumers.delete(state); | ||
| 502 | + self.#deleteConsumer(state); | ||
| 446 | 503 | return { __proto__: null, done: true, value: undefined }; | |
| 447 | 504 | } | |
| 448 | 505 | } | |
@@ -451,21 +508,25 @@ class SyncShareImpl { | |||
| 451 | 508 | ||
| 452 | 509 | if (self.#sourceError) { | |
| 453 | 510 | state.detached = true; | |
| 454 | - self.#consumers.delete(state); | ||
| 511 | + self.#deleteConsumer(state); | ||
| 455 | 512 | throw self.#sourceError; | |
| 456 | 513 | } | |
| 457 | 514 | ||
| 458 | 515 | const newBufferIndex = state.cursor - self.#bufferStart; | |
| 459 | 516 | if (newBufferIndex < self.#buffer.length) { | |
| 460 | 517 | const chunk = self.#buffer.get(newBufferIndex); | |
| 518 | + const cursor = state.cursor; | ||
| 461 | 519 | state.cursor++; | |
| 462 | - self.#tryTrimBuffer(); | ||
| 520 | + if (cursor === self.#cachedMinCursor && | ||
| 521 | + --self.#cachedMinCursorConsumers === 0) { | ||
| 522 | + self.#tryTrimBuffer(); | ||
| 523 | + } | ||
| 463 | 524 | return { __proto__: null, done: false, value: chunk }; | |
| 464 | 525 | } | |
| 465 | 526 | ||
| 466 | 527 | if (self.#sourceExhausted) { | |
| 467 | 528 | state.detached = true; | |
| 468 | - self.#consumers.delete(state); | ||
| 529 | + self.#deleteConsumer(state); | ||
| 469 | 530 | return { __proto__: null, done: true, value: undefined }; | |
| 470 | 531 | } | |
| 471 | 532 | ||
@@ -474,15 +535,17 @@ class SyncShareImpl { | |||
| 474 | 535 | ||
| 475 | 536 | return() { | |
| 476 | 537 | state.detached = true; | |
| 477 | - self.#consumers.delete(state); | ||
| 478 | - self.#tryTrimBuffer(); | ||
| 538 | + if (self.#deleteConsumer(state)) { | ||
| 539 | + self.#tryTrimBuffer(); | ||
| 540 | + } | ||
| 479 | 541 | return { __proto__: null, done: true, value: undefined }; | |
| 480 | 542 | }, | |
| 481 | 543 | ||
| 482 | 544 | throw() { | |
| 483 | 545 | state.detached = true; | |
| 484 | - self.#consumers.delete(state); | ||
| 485 | - self.#tryTrimBuffer(); | ||
| 546 | + if (self.#deleteConsumer(state)) { | ||
| 547 | + self.#tryTrimBuffer(); | ||
| 548 | + } | ||
| 486 | 549 | return { __proto__: null, done: true, value: undefined }; | |
| 487 | 550 | }, | |
| 488 | 551 | }; | |
@@ -532,13 +595,36 @@ class SyncShareImpl { | |||
| 532 | 595 | } | |
| 533 | 596 | ||
| 534 | 597 | #tryTrimBuffer() { | |
| 535 | - const minCursor = getMinCursor( | ||
| 536 | - this.#consumers, this.#bufferStart + this.#buffer.length); | ||
| 537 | - const trimCount = minCursor - this.#bufferStart; | ||
| 598 | + if (this.#cachedMinCursorConsumers === 0) { | ||
| 599 | + this.#recomputeMinCursor(); | ||
| 600 | + } | ||
| 601 | + const trimCount = this.#cachedMinCursor - this.#bufferStart; | ||
| 538 | 602 | if (trimCount > 0) { | |
| 539 | 603 | this.#buffer.trimFront(trimCount); | |
| 540 | - this.#bufferStart = minCursor; | ||
| 604 | + this.#bufferStart = this.#cachedMinCursor; | ||
| 605 | + } | ||
| 606 | + } | ||
| 607 | + | ||
| 608 | + #recomputeMinCursor() { | ||
| 609 | + const { minCursor, minCursorConsumers } = getMinCursor( | ||
| 610 | + this.#consumers, this.#bufferStart + this.#buffer.length); | ||
| 611 | + this.#cachedMinCursor = minCursor; | ||
| 612 | + this.#cachedMinCursorConsumers = minCursorConsumers; | ||
| 613 | + } | ||
| 614 | + | ||
| 615 | + #deleteConsumerFromMin(consumer) { | ||
| 616 | + if (consumer.cursor === this.#cachedMinCursor) { | ||
| 617 | + this.#cachedMinCursorConsumers--; | ||
| 618 | + return this.#cachedMinCursorConsumers === 0; | ||
| 619 | + } | ||
| 620 | + return false; | ||
| 621 | + } | ||
| 622 | + | ||
| 623 | + #deleteConsumer(consumer) { | ||
| 624 | + if (this.#consumers.delete(consumer)) { | ||
| 625 | + return this.#deleteConsumerFromMin(consumer); | ||
| 541 | 626 | } | |
| 627 | + return false; | ||
| 542 | 628 | } | |
| 543 | 629 | } | |
| 544 | 630 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -70,20 +70,24 @@ function onSignalAbort(signal, handler) { | |||
| 70 | 70 | } | |
| 71 | 71 | ||
| 72 | 72 | /** | |
| 73 | - * Compute the minimum cursor across a set of consumers. | ||
| 74 | - * Returns fallback if the set is empty. | ||
| 73 | + * Compute the minimum cursor across a set of consumers and count how many | ||
| 74 | + * consumers are at that cursor. | ||
| 75 | 75 | * @param {Set} consumers - Set of objects with a `cursor` property | |
| 76 | - * @param {number} fallback - Value to return when set is empty | ||
| 77 | - * @returns {number} | ||
| 76 | + * @param {number} fallback - Cursor to return when set is empty | ||
| 77 | + * @returns {{ minCursor: number, minCursorConsumers: number }} | ||
| 78 | 78 | */ | |
| 79 | 79 | function getMinCursor(consumers, fallback) { | |
| 80 | - let min = Infinity; | ||
| 80 | + let minCursor = fallback; | ||
| 81 | + let minCursorConsumers = 0; | ||
| 81 | 82 | for (const consumer of consumers) { | |
| 82 | - if (consumer.cursor < min) { | ||
| 83 | - min = consumer.cursor; | ||
| 83 | + if (consumer.cursor < minCursor) { | ||
| 84 | + minCursor = consumer.cursor; | ||
| 85 | + minCursorConsumers = 1; | ||
| 86 | + } else if (consumer.cursor === minCursor) { | ||
| 87 | + minCursorConsumers++; | ||
| 84 | 88 | } | |
| 85 | 89 | } | |
| 86 | - return min === Infinity ? fallback : min; | ||
| 90 | + return { __proto__: null, minCursor, minCursorConsumers }; | ||
| 87 | 91 | } | |
| 88 | 92 | ||
| 89 | 93 | /** | |
| Back | FazBrowse Home | New Git URL |
0 commit comments