@nxtedition/shared 4.0.3 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -183,7 +183,7 @@ Immediately publishes the pending write position to the reader. Unlike `uncork`,
183
183
 
184
184
  ## Benchmarks
185
185
 
186
- Measured on AMD EPYC 9355P (4.28 GHz), Node.js 25.6.0, 8 MiB ring buffer, Docker (x64-linux).
186
+ Measured on AMD EPYC 9355P (4.29 GHz), Node.js 25.6.1, 8 MiB ring buffer, Docker (x64-linux).
187
187
 
188
188
  Each benchmark writes batches of fixed-size messages from the main thread and
189
189
  reads them in a worker thread. The shared ring buffer is compared against
@@ -193,38 +193,39 @@ Node.js `postMessage` (structured clone).
193
193
 
194
194
  | Size | shared (buffer) | shared (string) | postMessage (buffer) | postMessage (string) |
195
195
  | -----: | --------------: | --------------: | -------------------: | -------------------: |
196
- | 64 B | **901 MiB/s** | 410 MiB/s | 25 MiB/s | 42 MiB/s |
197
- | 256 B | **2.67 GiB/s** | 896 MiB/s | 88 MiB/s | 158 MiB/s |
198
- | 1 KiB | **4.88 GiB/s** | 1.26 GiB/s | 328 MiB/s | 498 MiB/s |
199
- | 4 KiB | **9.22 GiB/s** | 1.50 GiB/s | 1.14 GiB/s | 1.70 GiB/s |
200
- | 16 KiB | **10.90 GiB/s** | 1.56 GiB/s | 4.29 GiB/s | 6.27 GiB/s |
201
- | 64 KiB | 13.03 GiB/s | 1.55 GiB/s | 10.10 GiB/s | **15.18 GiB/s** |
196
+ | 64 B | **838 MiB/s** | 388 MiB/s | 24 MiB/s | 42 MiB/s |
197
+ | 256 B | **2.65 GiB/s** | 1.46 GiB/s | 89 MiB/s | 168 MiB/s |
198
+ | 1 KiB | **4.95 GiB/s** | 4.86 GiB/s | 339 MiB/s | 525 MiB/s |
199
+ | 4 KiB | 8.42 GiB/s | **15.11 GiB/s** | 1.12 GiB/s | 1.86 GiB/s |
200
+ | 16 KiB | 12.02 GiB/s | **33.27 GiB/s** | 4.12 GiB/s | 6.02 GiB/s |
201
+ | 64 KiB | 12.96 GiB/s | **43.66 GiB/s** | 9.33 GiB/s | 14.73 GiB/s |
202
202
 
203
203
  ### Message rate
204
204
 
205
205
  | Size | shared (buffer) | shared (string) | postMessage (buffer) | postMessage (string) |
206
206
  | -----: | --------------: | --------------: | -------------------: | -------------------: |
207
- | 64 B | **14.76 M/s** | 6.72 M/s | 405 K/s | 688 K/s |
208
- | 256 B | **11.20 M/s** | 3.67 M/s | 360 K/s | 648 K/s |
209
- | 1 KiB | **5.12 M/s** | 1.32 M/s | 336 K/s | 510 K/s |
210
- | 4 KiB | **2.42 M/s** | 394 K/s | 298 K/s | 445 K/s |
211
- | 16 KiB | **714 K/s** | 102 K/s | 281 K/s | 411 K/s |
212
- | 64 KiB | 213 K/s | 25 K/s | 165 K/s | **249 K/s** |
207
+ | 64 B | **13.73 M/s** | 6.35 M/s | 391 K/s | 693 K/s |
208
+ | 256 B | **11.14 M/s** | 6.14 M/s | 366 K/s | 689 K/s |
209
+ | 1 KiB | **5.19 M/s** | 5.09 M/s | 348 K/s | 538 K/s |
210
+ | 4 KiB | 2.21 M/s | **3.96 M/s** | 295 K/s | 488 K/s |
211
+ | 16 KiB | 788 K/s | **2.18 M/s** | 270 K/s | 395 K/s |
212
+ | 64 KiB | 212 K/s | **715 K/s** | 153 K/s | 241 K/s |
213
213
 
214
214
  ### Key findings
215
215
 
216
216
  - **Small messages (64–256 B):** The shared ring buffer with `Buffer.set` delivers
217
- **14.8–11.2 M msg/s** — up to **36x faster** than `postMessage` (buffer) and
218
- **21x faster** than `postMessage` (string). Per-message overhead dominates at
217
+ **13.7–11.1 M msg/s** — up to **35x faster** than `postMessage` (buffer) and
218
+ **20x faster** than `postMessage` (string). Per-message overhead dominates at
219
219
  these sizes, and avoiding structured cloning makes the biggest difference.
220
220
 
221
- - **Medium to large messages (1–16 KiB):** `Buffer.set` via the ring buffer
222
- maintains its lead, reaching **10.9 GiB/s** at 16 KiB **1.7–5.4x faster**
223
- than the best `postMessage` variant.
221
+ - **Medium messages (1 KiB):** `Buffer.set` and string are nearly identical
222
+ (**4.95 vs 4.86 GiB/s**), both **~9x faster** than the best `postMessage`
223
+ variant.
224
224
 
225
- - **Very large messages (64 KiB):** `postMessage` (string) overtakes the shared
226
- buffer at **15.2 GiB/s** vs **13.0 GiB/s**. At this size, structured cloning
227
- overhead is amortized and the kernel's optimized `memcpy` dominates.
225
+ - **Large messages (4–64 KiB):** Shared string overtakes `Buffer.set` and
226
+ scales to **43.7 GiB/s** at 64 KiB — **3.4x faster** than `Buffer.set` and
227
+ **3.0x faster** than `postMessage` (string). At every size, the shared ring
228
+ buffer outperforms `postMessage`.
228
229
 
229
230
  - **Caveat:** The string benchmark uses ASCII-only content. Multi-byte UTF-8
230
231
  strings will not hit V8's vectorized fast path and will be significantly slower.
package/lib/index.d.ts CHANGED
@@ -26,7 +26,8 @@ export declare class SharedStateBuffer extends SharedArrayBuffer {
26
26
  export declare class Reader {
27
27
  #private;
28
28
  constructor(sharedBuffer: SharedArrayBuffer);
29
- readSome<U>(next: (data: BufferRegion, opaque?: U) => void | boolean, opaque?: U): number;
29
+ readSome(next: (data: BufferRegion) => void | boolean): number;
30
+ readSome<U>(next: (data: BufferRegion, opaque: U) => void | boolean, opaque: U): number;
30
31
  }
31
32
  /**
32
33
  * Writer for the ring buffer.
package/lib/index.js CHANGED
@@ -16,6 +16,8 @@ const STATE_BYTES = 128
16
16
  const HWM_BYTES = 256 * 1024 // 256 KiB
17
17
  const HWM_COUNT = 1024 // 1024 items
18
18
 
19
+ const isProduction = process.env.NODE_ENV === 'production'
20
+
19
21
 
20
22
 
21
23
 
@@ -59,7 +61,6 @@ export class Reader {
59
61
  #size
60
62
  #int32
61
63
  #data
62
- #readPos
63
64
 
64
65
  constructor(sharedBuffer ) {
65
66
  const size = sharedBuffer.byteLength - STATE_BYTES
@@ -77,13 +78,11 @@ export class Reader {
77
78
  byteOffset: 0,
78
79
  byteLength: 0,
79
80
  }
80
-
81
- // Local copy of the pointer. The `| 0` is a hint to the V8 JIT
82
- // compiler that this is a 32-bit integer, enabling optimizations.
83
- this.#readPos = Atomics.load(this.#state, READ_INDEX) | 0
84
81
  }
85
82
 
86
- readSome (next , opaque ) {
83
+
84
+
85
+ readSome (next , opaque ) {
87
86
  let count = 0
88
87
  let bytes = 0
89
88
 
@@ -91,14 +90,9 @@ export class Reader {
91
90
  const int32 = this.#int32
92
91
  const size = this.#size
93
92
  const data = this.#data
94
- let readPos = this.#readPos
95
- let writePos = state[WRITE_INDEX] | 0
96
93
 
97
- // First, check if the local writePos matches the readPos.
98
- // If so, refresh it from shared memory in case the writer has added data.
99
- if (readPos === writePos) {
100
- writePos = Atomics.load(state, WRITE_INDEX) | 0
101
- }
94
+ let readPos = state[READ_INDEX] | 0
95
+ let writePos = state[WRITE_INDEX] | 0
102
96
 
103
97
  // Process messages in a batch to minimize loop and atomic operation overhead.
104
98
  while (count < HWM_COUNT && bytes < HWM_BYTES && readPos !== writePos) {
@@ -113,7 +107,7 @@ export class Reader {
113
107
  readPos = 0
114
108
  // After wrapping, we must re-check against the writer's position.
115
109
  // It's possible the writer is now at a position > 0.
116
- writePos = Atomics.load(state, WRITE_INDEX) | 0
110
+ writePos = state[WRITE_INDEX] | 0
117
111
  } else {
118
112
  if (dataLen < 0) {
119
113
  throw new Error('Invalid data length')
@@ -140,12 +134,10 @@ export class Reader {
140
134
  }
141
135
  }
142
136
 
143
- this.#readPos = readPos
144
-
145
137
  // IMPORTANT: The reader only updates its shared `readPos` after a batch
146
- // is processed. This significantly reduces atomic operation overhead.
138
+ // is processed. This significantly reduces shared memory overhead.
147
139
  if (bytes > 0) {
148
- Atomics.store(state, READ_INDEX, readPos)
140
+ state[READ_INDEX] = readPos | 0
149
141
  }
150
142
 
151
143
  return count
@@ -192,8 +184,8 @@ export class Writer {
192
184
 
193
185
  // Local copies of the pointers. The `| 0` is a hint to the V8 JIT
194
186
  // compiler that these are 32-bit integers, enabling optimizations.
195
- this.#readPos = Atomics.load(this.#state, READ_INDEX) | 0
196
- this.#writePos = Atomics.load(this.#state, WRITE_INDEX) | 0
187
+ this.#readPos = this.#state[READ_INDEX] | 0
188
+ this.#writePos = this.#state[WRITE_INDEX] | 0
197
189
 
198
190
  this.#yielding = 0
199
191
  this.#corked = 0
@@ -231,12 +223,11 @@ export class Writer {
231
223
  if (delay > 0) {
232
224
  Atomics.wait(this.#state, READ_INDEX, this.#readPos, delay)
233
225
  } else {
234
- // @ts-expect-error Atomics.pause is Stage 3, available in Node.js 25+
235
226
  Atomics.pause()
236
227
  }
237
228
 
238
229
  // After waking up, refresh the local view of the reader's position.
239
- this.#readPos = Atomics.load(this.#state, READ_INDEX) | 0
230
+ this.#readPos = this.#state[READ_INDEX] | 0
240
231
  }
241
232
 
242
233
  /**
@@ -260,12 +251,12 @@ export class Writer {
260
251
  }
261
252
 
262
253
  this.#readPos = state[READ_INDEX] | 0
263
- if (this.#readPos === 0) {
254
+ if (this.#readPos < 4) {
264
255
  this.#yield(0)
265
256
  }
266
257
 
267
258
  // Not enough space at the end. Check if there's space at the beginning.
268
- if (this.#readPos === 0) {
259
+ if (this.#readPos < 4) {
269
260
  // Reader is at the beginning, so no space to wrap around into.
270
261
  return false
271
262
  }
@@ -276,13 +267,13 @@ export class Writer {
276
267
  // Reset writer position to the beginning.
277
268
  this.#writePos = 0
278
269
 
279
- if (this.#writePos + 4 > size) {
270
+ if (!isProduction && this.#writePos + 4 > size) {
280
271
  // assertion
281
272
  throw new Error(
282
273
  `Write position ${this.#writePos} with next header exceeds buffer size ${size}`,
283
274
  )
284
275
  }
285
- if (this.#writePos === this.#readPos) {
276
+ if (!isProduction && this.#writePos === this.#readPos) {
286
277
  // assertion
287
278
  throw new Error(
288
279
  `Write position ${this.#writePos} cannot equal read position ${this.#readPos}`,
@@ -290,6 +281,7 @@ export class Writer {
290
281
  }
291
282
 
292
283
  Atomics.store(state, WRITE_INDEX, this.#writePos)
284
+ this.#pending = 0
293
285
  }
294
286
 
295
287
  // Case 2: The writer has wrapped around. [ 0 ... W - R ... s ]
@@ -329,7 +321,7 @@ export class Writer {
329
321
  }
330
322
 
331
323
  const size = this.#size
332
- if (dataPos + dataLen > size) {
324
+ if (!isProduction && dataPos + dataLen > size) {
333
325
  // assertion
334
326
  throw new Error(`Data position ${dataPos} with length ${dataLen} exceeds buffer size ${size}`)
335
327
  }
@@ -337,11 +329,11 @@ export class Writer {
337
329
  const alignedLen = (dataLen + 3) & ~3
338
330
  const nextPos = this.#writePos + 4 + alignedLen
339
331
 
340
- if (nextPos + 4 > size) {
332
+ if (!isProduction && nextPos + 4 > size) {
341
333
  // assertion
342
334
  throw new Error(`Write position ${nextPos} with next header exceeds buffer size ${size}`)
343
335
  }
344
- if (nextPos === this.#readPos) {
336
+ if (!isProduction && nextPos === this.#readPos) {
345
337
  // assertion
346
338
  throw new Error(`Write position ${nextPos} cannot equal read position ${this.#readPos}`)
347
339
  }
@@ -413,7 +405,8 @@ export class Writer {
413
405
 
414
406
  this.#write(len, fn, opaque)
415
407
 
416
- if (this.#writePos === this.#readPos) {
408
+ if (!isProduction && this.#writePos === this.#readPos) {
409
+ // assertion
417
410
  throw new Error(
418
411
  `Write position ${this.#writePos} cannot equal read position ${this.#readPos}`,
419
412
  )
@@ -447,7 +440,8 @@ export class Writer {
447
440
 
448
441
  this.#write(len, fn, opaque)
449
442
 
450
- if (this.#writePos === this.#readPos) {
443
+ if (!isProduction && this.#writePos === this.#readPos) {
444
+ // assertion
451
445
  throw new Error(
452
446
  `Write position ${this.#writePos} cannot equal read position ${this.#readPos}`,
453
447
  )
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/shared",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -27,5 +27,5 @@
27
27
  "rimraf": "^6.1.3",
28
28
  "typescript": "^5.9.3"
29
29
  },
30
- "gitHead": "239c3eeff1f228d4d71ff6f8c884642f359065b5"
30
+ "gitHead": "f6592f0be62fecc161237610ae6454ec6585548b"
31
31
  }