@daneren2005/shared-memory-objects 0.0.21 → 1.1.0
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/LICENSE +21 -0
- package/README.md +167 -167
- package/dist/allocated-memory.js +38 -0
- package/dist/allocated-memory.js.map +1 -0
- package/dist/cached-item-list.js +76 -0
- package/dist/cached-item-list.js.map +1 -0
- package/dist/interfaces/pow2.js +2 -0
- package/dist/interfaces/pow2.js.map +1 -0
- package/dist/interfaces/typed-array-constructor.js +2 -0
- package/dist/interfaces/typed-array-constructor.js.map +1 -0
- package/dist/interfaces/typed-array.js +2 -0
- package/dist/interfaces/typed-array.js.map +1 -0
- package/dist/local-pool.js +95 -0
- package/dist/local-pool.js.map +1 -0
- package/dist/lock/read-write-lock.js +25 -0
- package/dist/lock/read-write-lock.js.map +1 -0
- package/dist/lock/simple-lock.js +14 -0
- package/dist/lock/simple-lock.js.map +1 -0
- package/dist/memory-buffer.js +327 -0
- package/dist/memory-buffer.js.map +1 -0
- package/dist/memory-heap.js +116 -0
- package/dist/memory-heap.js.map +1 -0
- package/dist/serialize-object.js +59 -0
- package/dist/serialize-object.js.map +1 -0
- package/dist/shared-list.js +165 -0
- package/dist/shared-list.js.map +1 -0
- package/dist/shared-map.js +140 -0
- package/dist/shared-map.js.map +1 -0
- package/dist/shared-pointer-list.js +50 -0
- package/dist/shared-pointer-list.js.map +1 -0
- package/dist/shared-pool.js +165 -0
- package/dist/shared-pool.js.map +1 -0
- package/dist/shared-string.js +79 -0
- package/dist/shared-string.js.map +1 -0
- package/dist/shared-vector.js +165 -0
- package/dist/shared-vector.js.map +1 -0
- package/dist/src/memory-heap.d.ts +0 -1
- package/dist/src/utils/atomic-math.d.ts +4 -4
- package/dist/src/utils/typedarray.d.ts +278 -278
- package/dist/utils/16-from-32-array.js +20 -0
- package/dist/utils/16-from-32-array.js.map +1 -0
- package/dist/utils/16-from-64-array.js +12 -0
- package/dist/utils/16-from-64-array.js.map +1 -0
- package/dist/utils/atomic-math.js +36 -0
- package/dist/utils/atomic-math.js.map +1 -0
- package/dist/utils/float32-atomics.js +28 -0
- package/dist/utils/float32-atomics.js.map +1 -0
- package/dist/utils/float64-atomics.js +28 -0
- package/dist/utils/float64-atomics.js.map +1 -0
- package/dist/utils/pointer.js +44 -0
- package/dist/utils/pointer.js.map +1 -0
- package/dist/utils/typedarray.js +46 -0
- package/dist/utils/typedarray.js.map +1 -0
- package/package.json +65 -72
- package/dist/shared-memory-objects.js +0 -1674
- package/dist/shared-memory-objects.js.map +0 -1
- package/dist/shared-memory-objects.umd.cjs +0 -2
- package/dist/shared-memory-objects.umd.cjs.map +0 -1
- package/dist/src/main.d.ts +0 -22
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Jackson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
# Shared Memory Objects
|
|
2
|
-
A library to try to make making a multi-threaded game in Javascript possible. This package is to provide a wrapper to create objects and data structures that are backed by a SharedArrayBuffer and can be shared between multiple threads. The end result is a package that has all of the slowness of Javascript with all of the baggage of dealing with manual memory allocations. If you need to multi-thread you are probably better of just using a different language and compiling to WebAssembly. But if you, like me, just want to use Javascript/Typescript and are willing to deal with dealing with manual memory allocations then this library could save you some time.
|
|
3
|
-
|
|
4
|
-
A demo can be found at https://daneren2005.github.io/ecs-sharedarraybuffer-playground/#/shared-memory-objects
|
|
5
|
-
The code is at https://github.com/daneren2005/ecs-sharedarraybuffer-playground/tree/dev/src/shared-memory-objects
|
|
6
|
-
|
|
7
|
-
## Basics
|
|
8
|
-
The core of this package is the MemoryHeap. You should usually just have a single heap that is shared between all of your different threads. Each heap can have multiple MemoryBuffers. By default each buffer is only 8KB but it can be configured up to 1MB, and you can have up to 4k buffers for a total of 4GB. When you allocate memory, if there is not enough space it will allocate another buffers automatically. When allocating memory, you will get a AllocatedMemory object that is a wrapper around the allocated memory by calling `heap.allocUI32({count of 32 bit numbers})`. By default AllocatedMemory is backed by a Uint32Array but you can get any type of array from `AllocatedMemory.getArray(Int32Array);`.
|
|
9
|
-
|
|
10
|
-
Each allocated memory location can be stored as an int pointer. You can use `getPointer(int)` to get the bufferPosition (ie: buffer index in the heap) and bufferByteOffset that the memory location points to. You can also convert a bufferPosition/bufferByteOffset pair to an int pointer with `createPointer(bufferPosition, bufferByteOffset)`. The pointer format is uses 12 bits for the buffer index and the remaining 20 bits for the byte offset in that buffer for a total of 1MB per buffer and 4GB total of memory. Each allocated memory object can return either a pointer via `allocatedMemory.pointer` or the raw position/byte offset via `allocatedMemory.getSharedMemory()`.
|
|
11
|
-
|
|
12
|
-
When passing memory to another thread you can either pass a pointer or a serialized version of the buffer position/byte offset in order to re-create the object in the other thread.
|
|
13
|
-
|
|
14
|
-
## Getting Started
|
|
15
|
-
`npm install @daneren2005/shared-memory-objects`
|
|
16
|
-
|
|
17
|
-
Example to update blocks of memory from a thread.
|
|
18
|
-
```
|
|
19
|
-
let heap = new MemoryHeap();
|
|
20
|
-
let memory = heap.allocUI32(4);
|
|
21
|
-
|
|
22
|
-
// Pass memory to another thread
|
|
23
|
-
thread.postMessage({
|
|
24
|
-
heap: heap.getSharedMemory(),
|
|
25
|
-
memory: memory.getSharedMemory()
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// From worker thread re-construct memory and change it
|
|
29
|
-
self.onmessage = (e) => {
|
|
30
|
-
let heap = new MemoryHeap(e.data.heap);
|
|
31
|
-
let memory = new AllocatedMemory(heap, e.data.memory);
|
|
32
|
-
memory.data[2] = 5;
|
|
33
|
-
};
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
// Example to work with data structures from a thread. When constructing a new structure you just pass the heap. When re-creating a structure from an already initialized memory location pass the heap and the shared memory location for it.
|
|
37
|
-
```
|
|
38
|
-
let heap = new MemoryHeap();
|
|
39
|
-
let list = new SharedList(heap);
|
|
40
|
-
|
|
41
|
-
// Pass memory to another thread
|
|
42
|
-
thread.postMessage({
|
|
43
|
-
heap: heap.getSharedMemory(),
|
|
44
|
-
list: list.getSharedMemory()
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// From worker thread re-construct memory and change it
|
|
48
|
-
self.onmessage = (e) => {
|
|
49
|
-
let heap = new MemoryHeap(e.data.heap);
|
|
50
|
-
let list = new SharedList(heap, e.data.list);
|
|
51
|
-
|
|
52
|
-
list.push(5);
|
|
53
|
-
};
|
|
54
|
-
```
|
|
55
|
-
let mainList = new SharedList(memory);
|
|
56
|
-
let secondList = new SharedList(memory, mainList.getSharedMemory());
|
|
57
|
-
|
|
58
|
-
## Data Structures
|
|
59
|
-
- SharedList - linked list
|
|
60
|
-
- SharedVector - growable array
|
|
61
|
-
- SharedMap - growable hash map
|
|
62
|
-
- LocalPool - stable indexed data with a recycled pool that can only be pushed/deleted from main thread but can pass TypedArrays to other threads to operate on
|
|
63
|
-
- SharedPool - stable indexed data with a recycled pool and maximum internal array sizes
|
|
64
|
-
- SharedString
|
|
65
|
-
|
|
66
|
-
## Thread Safety
|
|
67
|
-
- Memory allocations is thread safe as long as it does not need to create a new buffer. Right now that can only be done from the main thread.
|
|
68
|
-
- SharedList, SharedVector, SharedPool, and SharedMap are all not thread safe.
|
|
69
|
-
- SharedString is thread safe with a lock on read/write with a cached version of the string so it doesn't need to lock after the first read unless the string has changed.
|
|
70
|
-
|
|
71
|
-
## TODO
|
|
72
|
-
- Make creating new buffers from allocations possible from multiple threads
|
|
73
|
-
- Make data structures thread safe
|
|
74
|
-
- Add basic thread safe object example
|
|
75
|
-
|
|
76
|
-
## Performance
|
|
77
|
-
The tl;dr is that none of these data structures are close to what you can get by just using native data structures, but I wasn't expecting them to be with their overhead.
|
|
78
|
-
They are all significantly slower at iterating and accessing an indexed location. The SharedList is slowest at everything.
|
|
79
|
-
The SharedPool/LocalPool is the closest to native performance when doing a bunch of random deletes and inserts, which is what I use it for as the memory storage for components in my own ECS framework.
|
|
80
|
-
Since indexed access is about 1/4 the speed of just using a native JS array, there needs to be a lot of work offloaded into a separate thread to make it worth it.
|
|
81
|
-
|
|
82
|
-
Shared Data Structures: 10000 iterations
|
|
83
|
-
```
|
|
84
|
-
name hz min max mean p75 p99 p995 p999 rme samples
|
|
85
|
-
shared list 1,040.61 0.7898 2.1673 0.9610 1.0080 1.6299 1.7740 2.1673 ±1.49% 521
|
|
86
|
-
shared vector 2,447.08 0.3489 0.8944 0.4086 0.4369 0.7289 0.7887 0.8457 ±1.12% 1224
|
|
87
|
-
local pool 2,661.05 0.3266 0.9810 0.3758 0.3751 0.6516 0.7423 0.9332 ±1.05% 1331
|
|
88
|
-
shared pool 2,052.60 0.4255 1.1583 0.4872 0.4878 0.8323 0.8735 1.0186 ±1.10% 1027
|
|
89
|
-
native array 379,950.63 0.0021 0.1505 0.0026 0.0023 0.0062 0.0070 0.0218 ±0.28% 189976
|
|
90
|
-
|
|
91
|
-
native array
|
|
92
|
-
142.78x faster than local pool
|
|
93
|
-
155.27x faster than shared vector
|
|
94
|
-
185.11x faster than shared pool
|
|
95
|
-
365.12x faster than shared list
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Shared Data Structures: 1000 indexed locations
|
|
99
|
-
```
|
|
100
|
-
name hz min max mean p75 p99 p995 p999 rme samples
|
|
101
|
-
shared vector 28,713.93 0.0276 1.1649 0.0348 0.0335 0.0767 0.1276 0.2245 ±0.86% 14357
|
|
102
|
-
local pool 88,708.78 0.0099 0.2418 0.0113 0.0105 0.0192 0.0261 0.1700 ±0.62% 44369
|
|
103
|
-
shared pool 33,795.16 0.0270 0.1868 0.0296 0.0287 0.0536 0.0631 0.1376 ±0.36% 16898
|
|
104
|
-
native array 126,855.06 0.0072 0.2046 0.0079 0.0076 0.0129 0.0146 0.0993 ±0.42% 63428
|
|
105
|
-
|
|
106
|
-
native array
|
|
107
|
-
1.43x faster than local pool
|
|
108
|
-
3.75x faster than shared pool
|
|
109
|
-
4.42x faster than shared vector
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Shared Data Structures: 1000 inserts
|
|
113
|
-
```
|
|
114
|
-
name hz min max mean p75 p99 p995 p999 rme samples
|
|
115
|
-
shared list 1,989.04 0.4176 1.2361 0.5028 0.5238 0.9547 1.0934 1.2361 ±1.49% 995
|
|
116
|
-
shared map 98.3711 8.8636 13.3231 10.1656 10.7163 13.3231 13.3231 13.3231 ±3.14% 50
|
|
117
|
-
shared vector 15,559.65 0.0529 0.4758 0.0643 0.0609 0.1405 0.1849 0.2850 ±0.71% 7780
|
|
118
|
-
shared vector with correct amount initialized 22,386.17 0.0372 0.3258 0.0447 0.0415 0.0968 0.1167 0.2119 ±0.61% 11194
|
|
119
|
-
local pool 43,012.42 0.0173 0.3407 0.0232 0.0210 0.0706 0.0933 0.2009 ±0.79% 21507
|
|
120
|
-
shared pool 9,873.77 0.0794 0.5068 0.1013 0.1013 0.2696 0.3232 0.4319 ±1.06% 4937
|
|
121
|
-
shared pool with already deleted elements 13,396.59 0.0643 0.3341 0.0746 0.0700 0.1830 0.2322 0.2799 ±0.70% 6699
|
|
122
|
-
native array 94,368.06 0.0084 0.4662 0.0106 0.0101 0.0236 0.0498 0.1181 ±0.61% 47186
|
|
123
|
-
|
|
124
|
-
native array
|
|
125
|
-
2.19x faster than local pool
|
|
126
|
-
4.22x faster than shared vector with correct amount initialized
|
|
127
|
-
6.06x faster than shared vector
|
|
128
|
-
7.04x faster than shared pool with already deleted elements
|
|
129
|
-
9.56x faster than shared pool
|
|
130
|
-
47.44x faster than shared list
|
|
131
|
-
959.31x faster than shared map
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Shared Data Structures: 1000 deletes random element
|
|
135
|
-
NOTE: Local pool is faster than native because array slice is slower than push/pop to a native recycle array
|
|
136
|
-
```
|
|
137
|
-
name hz min max mean p75 p99 p995 p999 rme samples
|
|
138
|
-
shared list 13.1818 68.1220 83.8157 75.8622 79.3236 83.8157 83.8157 83.8157 ±4.40% 10
|
|
139
|
-
shared vector 387.53 2.3857 3.0819 2.5804 2.6409 3.0624 3.0819 3.0819 ±0.70% 194
|
|
140
|
-
local pool 71,370.27 0.0105 0.5406 0.0140 0.0122 0.1055 0.1351 0.1960 ±1.07% 35686
|
|
141
|
-
shared pool 13,049.38 0.0689 0.2990 0.0766 0.0746 0.1439 0.1681 0.2269 ±0.51% 6525
|
|
142
|
-
native array 10,559.95 0.0827 0.2363 0.0947 0.0941 0.1532 0.1693 0.2037 ±0.45% 5281
|
|
143
|
-
|
|
144
|
-
local pool
|
|
145
|
-
5.47x faster than shared pool
|
|
146
|
-
6.76x faster than native array
|
|
147
|
-
184.17x faster than shared vector
|
|
148
|
-
5414.30x faster than shared list
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Shared Data Structures: 1000 insert and deleting random elements
|
|
152
|
-
```
|
|
153
|
-
name hz min max mean p75 p99 p995 p999 rme samples
|
|
154
|
-
shared list 20.5573 43.7463 57.7009 48.6445 50.4710 57.7009 57.7009 57.7009 ±5.73% 11
|
|
155
|
-
shared vector 752.07 1.0805 2.4937 1.3297 1.3769 2.1734 2.3537 2.4937 ±1.81% 377
|
|
156
|
-
local pool 22,453.72 0.0388 0.3417 0.0445 0.0417 0.1214 0.1574 0.2231 ±0.63% 11227
|
|
157
|
-
shared pool 5,684.68 0.1490 1.7416 0.1759 0.1703 0.4351 0.5387 0.9790 ±1.37% 2843
|
|
158
|
-
native array 11,030.10 0.0793 0.2980 0.0907 0.0866 0.1808 0.1976 0.2412 ±0.54% 5516
|
|
159
|
-
|
|
160
|
-
local pool
|
|
161
|
-
2.04x faster than native array
|
|
162
|
-
3.95x faster than shared pool
|
|
163
|
-
29.86x faster than shared vector
|
|
164
|
-
1092.25x faster than shared list
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## Credit
|
|
1
|
+
# Shared Memory Objects
|
|
2
|
+
A library to try to make making a multi-threaded game in Javascript possible. This package is to provide a wrapper to create objects and data structures that are backed by a SharedArrayBuffer and can be shared between multiple threads. The end result is a package that has all of the slowness of Javascript with all of the baggage of dealing with manual memory allocations. If you need to multi-thread you are probably better of just using a different language and compiling to WebAssembly. But if you, like me, just want to use Javascript/Typescript and are willing to deal with dealing with manual memory allocations then this library could save you some time.
|
|
3
|
+
|
|
4
|
+
A demo can be found at https://daneren2005.github.io/ecs-sharedarraybuffer-playground/#/shared-memory-objects
|
|
5
|
+
The code is at https://github.com/daneren2005/ecs-sharedarraybuffer-playground/tree/dev/src/shared-memory-objects
|
|
6
|
+
|
|
7
|
+
## Basics
|
|
8
|
+
The core of this package is the MemoryHeap. You should usually just have a single heap that is shared between all of your different threads. Each heap can have multiple MemoryBuffers. By default each buffer is only 8KB but it can be configured up to 1MB, and you can have up to 4k buffers for a total of 4GB. When you allocate memory, if there is not enough space it will allocate another buffers automatically. When allocating memory, you will get a AllocatedMemory object that is a wrapper around the allocated memory by calling `heap.allocUI32({count of 32 bit numbers})`. By default AllocatedMemory is backed by a Uint32Array but you can get any type of array from `AllocatedMemory.getArray(Int32Array);`.
|
|
9
|
+
|
|
10
|
+
Each allocated memory location can be stored as an int pointer. You can use `getPointer(int)` to get the bufferPosition (ie: buffer index in the heap) and bufferByteOffset that the memory location points to. You can also convert a bufferPosition/bufferByteOffset pair to an int pointer with `createPointer(bufferPosition, bufferByteOffset)`. The pointer format is uses 12 bits for the buffer index and the remaining 20 bits for the byte offset in that buffer for a total of 1MB per buffer and 4GB total of memory. Each allocated memory object can return either a pointer via `allocatedMemory.pointer` or the raw position/byte offset via `allocatedMemory.getSharedMemory()`.
|
|
11
|
+
|
|
12
|
+
When passing memory to another thread you can either pass a pointer or a serialized version of the buffer position/byte offset in order to re-create the object in the other thread.
|
|
13
|
+
|
|
14
|
+
## Getting Started
|
|
15
|
+
`npm install @daneren2005/shared-memory-objects`
|
|
16
|
+
|
|
17
|
+
Example to update blocks of memory from a thread.
|
|
18
|
+
```
|
|
19
|
+
let heap = new MemoryHeap();
|
|
20
|
+
let memory = heap.allocUI32(4);
|
|
21
|
+
|
|
22
|
+
// Pass memory to another thread
|
|
23
|
+
thread.postMessage({
|
|
24
|
+
heap: heap.getSharedMemory(),
|
|
25
|
+
memory: memory.getSharedMemory()
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// From worker thread re-construct memory and change it
|
|
29
|
+
self.onmessage = (e) => {
|
|
30
|
+
let heap = new MemoryHeap(e.data.heap);
|
|
31
|
+
let memory = new AllocatedMemory(heap, e.data.memory);
|
|
32
|
+
memory.data[2] = 5;
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
// Example to work with data structures from a thread. When constructing a new structure you just pass the heap. When re-creating a structure from an already initialized memory location pass the heap and the shared memory location for it.
|
|
37
|
+
```
|
|
38
|
+
let heap = new MemoryHeap();
|
|
39
|
+
let list = new SharedList(heap);
|
|
40
|
+
|
|
41
|
+
// Pass memory to another thread
|
|
42
|
+
thread.postMessage({
|
|
43
|
+
heap: heap.getSharedMemory(),
|
|
44
|
+
list: list.getSharedMemory()
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// From worker thread re-construct memory and change it
|
|
48
|
+
self.onmessage = (e) => {
|
|
49
|
+
let heap = new MemoryHeap(e.data.heap);
|
|
50
|
+
let list = new SharedList(heap, e.data.list);
|
|
51
|
+
|
|
52
|
+
list.push(5);
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
let mainList = new SharedList(memory);
|
|
56
|
+
let secondList = new SharedList(memory, mainList.getSharedMemory());
|
|
57
|
+
|
|
58
|
+
## Data Structures
|
|
59
|
+
- SharedList - linked list
|
|
60
|
+
- SharedVector - growable array
|
|
61
|
+
- SharedMap - growable hash map
|
|
62
|
+
- LocalPool - stable indexed data with a recycled pool that can only be pushed/deleted from main thread but can pass TypedArrays to other threads to operate on
|
|
63
|
+
- SharedPool - stable indexed data with a recycled pool and maximum internal array sizes
|
|
64
|
+
- SharedString
|
|
65
|
+
|
|
66
|
+
## Thread Safety
|
|
67
|
+
- Memory allocations is thread safe as long as it does not need to create a new buffer. Right now that can only be done from the main thread.
|
|
68
|
+
- SharedList, SharedVector, SharedPool, and SharedMap are all not thread safe.
|
|
69
|
+
- SharedString is thread safe with a lock on read/write with a cached version of the string so it doesn't need to lock after the first read unless the string has changed.
|
|
70
|
+
|
|
71
|
+
## TODO
|
|
72
|
+
- Make creating new buffers from allocations possible from multiple threads
|
|
73
|
+
- Make data structures thread safe
|
|
74
|
+
- Add basic thread safe object example
|
|
75
|
+
|
|
76
|
+
## Performance
|
|
77
|
+
The tl;dr is that none of these data structures are close to what you can get by just using native data structures, but I wasn't expecting them to be with their overhead.
|
|
78
|
+
They are all significantly slower at iterating and accessing an indexed location. The SharedList is slowest at everything.
|
|
79
|
+
The SharedPool/LocalPool is the closest to native performance when doing a bunch of random deletes and inserts, which is what I use it for as the memory storage for components in my own ECS framework.
|
|
80
|
+
Since indexed access is about 1/4 the speed of just using a native JS array, there needs to be a lot of work offloaded into a separate thread to make it worth it.
|
|
81
|
+
|
|
82
|
+
Shared Data Structures: 10000 iterations
|
|
83
|
+
```
|
|
84
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
85
|
+
shared list 1,040.61 0.7898 2.1673 0.9610 1.0080 1.6299 1.7740 2.1673 ±1.49% 521
|
|
86
|
+
shared vector 2,447.08 0.3489 0.8944 0.4086 0.4369 0.7289 0.7887 0.8457 ±1.12% 1224
|
|
87
|
+
local pool 2,661.05 0.3266 0.9810 0.3758 0.3751 0.6516 0.7423 0.9332 ±1.05% 1331
|
|
88
|
+
shared pool 2,052.60 0.4255 1.1583 0.4872 0.4878 0.8323 0.8735 1.0186 ±1.10% 1027
|
|
89
|
+
native array 379,950.63 0.0021 0.1505 0.0026 0.0023 0.0062 0.0070 0.0218 ±0.28% 189976
|
|
90
|
+
|
|
91
|
+
native array
|
|
92
|
+
142.78x faster than local pool
|
|
93
|
+
155.27x faster than shared vector
|
|
94
|
+
185.11x faster than shared pool
|
|
95
|
+
365.12x faster than shared list
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Shared Data Structures: 1000 indexed locations
|
|
99
|
+
```
|
|
100
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
101
|
+
shared vector 28,713.93 0.0276 1.1649 0.0348 0.0335 0.0767 0.1276 0.2245 ±0.86% 14357
|
|
102
|
+
local pool 88,708.78 0.0099 0.2418 0.0113 0.0105 0.0192 0.0261 0.1700 ±0.62% 44369
|
|
103
|
+
shared pool 33,795.16 0.0270 0.1868 0.0296 0.0287 0.0536 0.0631 0.1376 ±0.36% 16898
|
|
104
|
+
native array 126,855.06 0.0072 0.2046 0.0079 0.0076 0.0129 0.0146 0.0993 ±0.42% 63428
|
|
105
|
+
|
|
106
|
+
native array
|
|
107
|
+
1.43x faster than local pool
|
|
108
|
+
3.75x faster than shared pool
|
|
109
|
+
4.42x faster than shared vector
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Shared Data Structures: 1000 inserts
|
|
113
|
+
```
|
|
114
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
115
|
+
shared list 1,989.04 0.4176 1.2361 0.5028 0.5238 0.9547 1.0934 1.2361 ±1.49% 995
|
|
116
|
+
shared map 98.3711 8.8636 13.3231 10.1656 10.7163 13.3231 13.3231 13.3231 ±3.14% 50
|
|
117
|
+
shared vector 15,559.65 0.0529 0.4758 0.0643 0.0609 0.1405 0.1849 0.2850 ±0.71% 7780
|
|
118
|
+
shared vector with correct amount initialized 22,386.17 0.0372 0.3258 0.0447 0.0415 0.0968 0.1167 0.2119 ±0.61% 11194
|
|
119
|
+
local pool 43,012.42 0.0173 0.3407 0.0232 0.0210 0.0706 0.0933 0.2009 ±0.79% 21507
|
|
120
|
+
shared pool 9,873.77 0.0794 0.5068 0.1013 0.1013 0.2696 0.3232 0.4319 ±1.06% 4937
|
|
121
|
+
shared pool with already deleted elements 13,396.59 0.0643 0.3341 0.0746 0.0700 0.1830 0.2322 0.2799 ±0.70% 6699
|
|
122
|
+
native array 94,368.06 0.0084 0.4662 0.0106 0.0101 0.0236 0.0498 0.1181 ±0.61% 47186
|
|
123
|
+
|
|
124
|
+
native array
|
|
125
|
+
2.19x faster than local pool
|
|
126
|
+
4.22x faster than shared vector with correct amount initialized
|
|
127
|
+
6.06x faster than shared vector
|
|
128
|
+
7.04x faster than shared pool with already deleted elements
|
|
129
|
+
9.56x faster than shared pool
|
|
130
|
+
47.44x faster than shared list
|
|
131
|
+
959.31x faster than shared map
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Shared Data Structures: 1000 deletes random element
|
|
135
|
+
NOTE: Local pool is faster than native because array slice is slower than push/pop to a native recycle array
|
|
136
|
+
```
|
|
137
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
138
|
+
shared list 13.1818 68.1220 83.8157 75.8622 79.3236 83.8157 83.8157 83.8157 ±4.40% 10
|
|
139
|
+
shared vector 387.53 2.3857 3.0819 2.5804 2.6409 3.0624 3.0819 3.0819 ±0.70% 194
|
|
140
|
+
local pool 71,370.27 0.0105 0.5406 0.0140 0.0122 0.1055 0.1351 0.1960 ±1.07% 35686
|
|
141
|
+
shared pool 13,049.38 0.0689 0.2990 0.0766 0.0746 0.1439 0.1681 0.2269 ±0.51% 6525
|
|
142
|
+
native array 10,559.95 0.0827 0.2363 0.0947 0.0941 0.1532 0.1693 0.2037 ±0.45% 5281
|
|
143
|
+
|
|
144
|
+
local pool
|
|
145
|
+
5.47x faster than shared pool
|
|
146
|
+
6.76x faster than native array
|
|
147
|
+
184.17x faster than shared vector
|
|
148
|
+
5414.30x faster than shared list
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Shared Data Structures: 1000 insert and deleting random elements
|
|
152
|
+
```
|
|
153
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
154
|
+
shared list 20.5573 43.7463 57.7009 48.6445 50.4710 57.7009 57.7009 57.7009 ±5.73% 11
|
|
155
|
+
shared vector 752.07 1.0805 2.4937 1.3297 1.3769 2.1734 2.3537 2.4937 ±1.81% 377
|
|
156
|
+
local pool 22,453.72 0.0388 0.3417 0.0445 0.0417 0.1214 0.1574 0.2231 ±0.63% 11227
|
|
157
|
+
shared pool 5,684.68 0.1490 1.7416 0.1759 0.1703 0.4351 0.5387 0.9790 ±1.37% 2843
|
|
158
|
+
native array 11,030.10 0.0793 0.2980 0.0907 0.0866 0.1808 0.1976 0.2412 ±0.54% 5516
|
|
159
|
+
|
|
160
|
+
local pool
|
|
161
|
+
2.04x faster than native array
|
|
162
|
+
3.95x faster than shared pool
|
|
163
|
+
29.86x faster than shared vector
|
|
164
|
+
1092.25x faster than shared list
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Credit
|
|
168
168
|
The entire core of this library is based on a fork of @thi.ng/malloc found at https://github.com/thi-ng/umbrella/blob/develop/packages/malloc. The only big difference between our MemoryBuffer and their MemPool is making allocations/freeing memory thread safe.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createPointer as r } from "./utils/pointer.js";
|
|
2
|
+
class u {
|
|
3
|
+
memory;
|
|
4
|
+
bufferPosition;
|
|
5
|
+
get bufferByteOffset() {
|
|
6
|
+
return this.data.byteOffset;
|
|
7
|
+
}
|
|
8
|
+
get pointer() {
|
|
9
|
+
return r(this.bufferPosition, this.bufferByteOffset);
|
|
10
|
+
}
|
|
11
|
+
buffer;
|
|
12
|
+
data;
|
|
13
|
+
constructor(t, e) {
|
|
14
|
+
this.memory = t, "buffer" in e ? (this.data = e.data, this.buffer = e.buffer, this.bufferPosition = this.memory.buffers.indexOf(e.buffer)) : (this.bufferPosition = e.bufferPosition, this.buffer = t.buffers[e.bufferPosition], this.data = new Uint32Array(this.buffer.buf, e.bufferByteOffset));
|
|
15
|
+
}
|
|
16
|
+
getArray(t, e, f) {
|
|
17
|
+
return new t(this.data.buffer, this.data.byteOffset + e * t.BYTES_PER_ELEMENT, f);
|
|
18
|
+
}
|
|
19
|
+
getArrayMemory(t, e) {
|
|
20
|
+
return {
|
|
21
|
+
bufferPosition: this.bufferPosition,
|
|
22
|
+
bufferByteOffset: this.bufferByteOffset + t * this.data.BYTES_PER_ELEMENT
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
free() {
|
|
26
|
+
this.buffer.free(this.data.byteOffset);
|
|
27
|
+
}
|
|
28
|
+
getSharedMemory() {
|
|
29
|
+
return {
|
|
30
|
+
bufferPosition: this.bufferPosition,
|
|
31
|
+
bufferByteOffset: this.bufferByteOffset
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
u as default
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=allocated-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allocated-memory.js","sources":["../src/allocated-memory.ts"],"sourcesContent":["import type { TypedArray } from './interfaces/typed-array';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryBuffer from './memory-buffer';\nimport type MemoryHeap from './memory-heap';\nimport { createPointer } from './utils/pointer';\n\nexport default class AllocatedMemory {\n\tprivate readonly memory: MemoryHeap;\n\n\treadonly bufferPosition: number;\n\tget bufferByteOffset(): number {\n\t\treturn this.data.byteOffset;\n\t}\n\tget pointer(): number {\n\t\treturn createPointer(this.bufferPosition, this.bufferByteOffset);\n\t}\n\tprivate buffer: MemoryBuffer;\n\tdata: Uint32Array;\n\n\tconstructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif('buffer' in config) {\n\t\t\tthis.data = config.data;\n\t\t\tthis.buffer = config.buffer;\n\t\t\tthis.bufferPosition = this.memory.buffers.indexOf(config.buffer);\n\t\t} else {\n\t\t\tthis.bufferPosition = config.bufferPosition;\n\t\t\tthis.buffer = memory.buffers[config.bufferPosition];\n\n\t\t\t// Making sure these are the correct size is slow but in dev we want to make sure we aren't allowing to go out of bounds\n\t\t\tif(import.meta.env.MODE === 'production') {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset);\n\t\t\t} else {\n\t\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, this.buffer.lengthOf(config.bufferByteOffset));\n\t\t\t}\n\t\t}\n\t}\n\n\tgetArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {\n\t\tif(import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {\n\t\t\tif((offset + length) * type.BYTES_PER_ELEMENT > this.data.byteLength) {\n\t\t\t\tconst message = `Trying to grab more memory from AllocatedMemory.getArray then we have: ${offset * type.BYTES_PER_ELEMENT} + ${length * type.BYTES_PER_ELEMENT} > ${this.data.byteLength}`;\n\t\t\t\tif(import.meta.env.MODE === 'test') {\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);\n\t}\n\tgetArrayMemory(offset: number, length: number): SharedAllocatedMemory {\n\t\tif(import.meta.env.MODE === 'development') {\n\t\t\tif(offset + length > this.data.length) {\n\t\t\t\tconsole.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT\n\t\t};\n\t}\n\n\tfree() {\n\t\t// NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free\n\t\tthis.buffer.free(this.data.byteOffset);\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset\n\t\t};\n\t}\n}\n\ninterface AllocatedMemoryConfig {\n\tdata: Uint32Array\n\tbuffer: MemoryBuffer\n}\n\ninterface SharedAllocatedMemory {\n\tbufferPosition: number\n\tbufferByteOffset: number\n}\nexport type { SharedAllocatedMemory };"],"names":["AllocatedMemory","createPointer","memory","config","type","offset","length"],"mappings":";AAMA,MAAqBA,EAAgB;AAAA,EACnB;AAAA,EAER;AAAA,EACT,IAAI,mBAA2B;AAC9B,WAAO,KAAK,KAAK;AAAA,EAClB;AAAA,EACA,IAAI,UAAkB;AACrB,WAAOC,EAAc,KAAK,gBAAgB,KAAK,gBAAgB;AAAA,EAChE;AAAA,EACQ;AAAA,EACR;AAAA,EAEA,YAAYC,GAAoBC,GAAuD;AACtF,SAAK,SAASD,GAEX,YAAYC,KACd,KAAK,OAAOA,EAAO,MACnB,KAAK,SAASA,EAAO,QACrB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,QAAQA,EAAO,MAAM,MAE/D,KAAK,iBAAiBA,EAAO,gBAC7B,KAAK,SAASD,EAAO,QAAQC,EAAO,cAAc,GAIjD,KAAK,OAAO,IAAI,YAAY,KAAK,OAAO,KAAKA,EAAO,gBAAgB;AAAA,EAKvE;AAAA,EAEA,SAA+BC,GAAgCC,GAAgBC,GAAmB;AAYjG,WAAO,IAAIF,EAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,aAAaC,IAASD,EAAK,mBAAmBE,CAAM;AAAA,EACjG;AAAA,EACA,eAAeD,GAAgBC,GAAuC;AAOrE,WAAO;AAAA,MACN,gBAAgB,KAAK;AAAA,MACrB,kBAAkB,KAAK,mBAAmBD,IAAS,KAAK,KAAK;AAAA,IAAA;AAAA,EAE/D;AAAA,EAEA,OAAO;AAEN,SAAK,OAAO,KAAK,KAAK,KAAK,UAAU;AAAA,EACtC;AAAA,EAEA,kBAAyC;AACxC,WAAO;AAAA,MACN,gBAAgB,KAAK;AAAA,MACrB,kBAAkB,KAAK;AAAA,IAAA;AAAA,EAEzB;AACD;"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import h from "./shared-list.js";
|
|
2
|
+
class c {
|
|
3
|
+
static ALLOCATE_COUNT = h.ALLOCATE_COUNT;
|
|
4
|
+
heap;
|
|
5
|
+
list;
|
|
6
|
+
cache = /* @__PURE__ */ new Map();
|
|
7
|
+
constructor(t, e) {
|
|
8
|
+
e ? this.list = new h(t, e) : this.list = new h(t), this.heap = t, this.list.onDelete = (i) => {
|
|
9
|
+
let r = Atomics.load(i, 0);
|
|
10
|
+
if (r) {
|
|
11
|
+
let s = this.cache.get(r);
|
|
12
|
+
s || (s = this.initItem(r)), s && (s.free(), this.cache.delete(r));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
get length() {
|
|
17
|
+
return this.list.length;
|
|
18
|
+
}
|
|
19
|
+
clear() {
|
|
20
|
+
this.list.clear(), this.cache.clear();
|
|
21
|
+
}
|
|
22
|
+
insert(t) {
|
|
23
|
+
this.list.insert(t.pointer), this.cache.set(t.pointer, t);
|
|
24
|
+
}
|
|
25
|
+
delete(t) {
|
|
26
|
+
return this.cache.delete(t.pointer), this.list.deleteValue(t.pointer);
|
|
27
|
+
}
|
|
28
|
+
getByPointer(t) {
|
|
29
|
+
let e = this.cache.get(t);
|
|
30
|
+
return e || (e = this.initItem(t), e && this.cache.set(t, e)), e;
|
|
31
|
+
}
|
|
32
|
+
*[Symbol.iterator]() {
|
|
33
|
+
let t = this.list[Symbol.iterator]();
|
|
34
|
+
for (let { data: e, deleteCurrent: i } of t) {
|
|
35
|
+
let r = Atomics.load(e, 0);
|
|
36
|
+
if (!r)
|
|
37
|
+
continue;
|
|
38
|
+
let s = this.cache.get(r);
|
|
39
|
+
s || (s = this.initItem(r), s && this.cache.set(r, s)), s && (yield {
|
|
40
|
+
item: s,
|
|
41
|
+
deleteCurrent: i
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
forEach(t, e) {
|
|
46
|
+
for (let { item: i } of this)
|
|
47
|
+
(!e || e(i)) && t(i);
|
|
48
|
+
}
|
|
49
|
+
find(t) {
|
|
50
|
+
for (let { item: e } of this)
|
|
51
|
+
if (t(e))
|
|
52
|
+
return e;
|
|
53
|
+
}
|
|
54
|
+
filter(t) {
|
|
55
|
+
let e = [];
|
|
56
|
+
for (let { item: i } of this)
|
|
57
|
+
t(i) && e.push(i);
|
|
58
|
+
return e;
|
|
59
|
+
}
|
|
60
|
+
map(t) {
|
|
61
|
+
const e = [];
|
|
62
|
+
for (let { item: i } of this)
|
|
63
|
+
e.push(t(i));
|
|
64
|
+
return e;
|
|
65
|
+
}
|
|
66
|
+
getSharedMemory() {
|
|
67
|
+
return this.list.getSharedMemory();
|
|
68
|
+
}
|
|
69
|
+
free() {
|
|
70
|
+
this.list.free(), this.cache.clear();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
c as default
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=cached-item-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-item-list.js","sources":["../src/cached-item-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport SharedList, { type SharedListMemory } from './shared-list';\n\nexport default abstract class CachedItemList<T extends Item> implements Iterable<{ item: T, deleteCurrent: () => void }> {\n\tstatic readonly ALLOCATE_COUNT = SharedList.ALLOCATE_COUNT;\n\n\tprotected heap: MemoryHeap;\n\tprotected list: SharedList;\n\tprotected cache: Map<number, T> = new Map();\n\n\tconstructor(heap: MemoryHeap, config?: CachedListConfig | SharedListMemory) {\n\t\tif(config) {\n\t\t\tthis.list = new SharedList(heap, config);\n\t\t} else {\n\t\t\tthis.list = new SharedList(heap);\n\t\t}\n\t\tthis.heap = heap;\n\n\t\tthis.list.onDelete = (pointerData: Uint32Array) => {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(pointer) {\n\t\t\t\tlet item = this.cache.get(pointer);\n\t\t\t\tif(!item) {\n\t\t\t\t\titem = this.initItem(pointer);\n\t\t\t\t}\n\n\t\t\t\tif(item) {\n\t\t\t\t\titem.free();\n\t\t\t\t\tthis.cache.delete(pointer);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tget length() {\n\t\treturn this.list.length;\n\t}\n\n\tclear() {\n\t\tthis.list.clear();\n\t\tthis.cache.clear();\n\t}\n\n\tinsert(item: T) {\n\t\tthis.list.insert(item.pointer);\n\t\tthis.cache.set(item.pointer, item);\n\t}\n\tdelete(item: T) {\n\t\tthis.cache.delete(item.pointer);\n\t\treturn this.list.deleteValue(item.pointer);\n\t}\n\n\tgetByPointer(pointer: number): T | undefined {\n\t\tlet item = this.cache.get(pointer);\n\t\tif(!item) {\n\t\t\titem = this.initItem(pointer);\n\t\t\tif(item) {\n\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t}\n\t\t}\n\n\t\treturn item;\n\t}\n\tprotected abstract initItem(pointer: number): T | undefined\n\n\t*[Symbol.iterator]() {\n\t\tlet iterator = this.list[Symbol.iterator]();\n\n\t\tfor(let { data: pointerData, deleteCurrent } of iterator) {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(!pointer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet item = this.cache.get(pointer);\n\t\t\tif(!item) {\n\t\t\t\titem = this.initItem(pointer);\n\t\t\t\tif(item) {\n\t\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(item) {\n\t\t\t\tyield {\n\t\t\t\t\titem,\n\t\t\t\t\tdeleteCurrent\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\tforEach(callback: (item: T) => void, filter?: (item: T) => boolean) {\n\t\tfor(let { item } of this) {\n\t\t\tif(!filter || filter(item)) {\n\t\t\t\tcallback(item);\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(callback: (item: T) => boolean): T | undefined {\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\treturn item;\n\t\t\t}\n\t\t}\n\t}\n\tfilter(callback: (entity: T) => boolean): Array<T> {\n\t\tlet items = [];\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\titems.push(item);\n\t\t\t}\n\t\t}\n\n\t\treturn items;\n\t}\n\tmap<X>(callback: (item: T) => X): Array<X> {\n\t\tconst array: Array<X> = [];\n\t\tfor(let { item } of this) {\n\t\t\tarray.push(callback(item));\n\t\t}\n\n\t\treturn array;\n\t}\n\n\tgetSharedMemory() {\n\t\treturn this.list.getSharedMemory();\n\t}\n\n\tfree() {\n\t\tthis.list.free();\n\t\tthis.cache.clear();\n\t}\n}\n\nexport interface CachedListConfig {\n\tinitWithBlock: SharedAllocatedMemory\n}\n\ninterface Item {\n\tpointer: number\n\tfree: () => void\n}"],"names":["CachedItemList","SharedList","heap","config","pointerData","pointer","item","iterator","deleteCurrent","callback","filter","items","array"],"mappings":";AAIA,MAA8BA,EAA2F;AAAA,EACxH,OAAgB,iBAAiBC,EAAW;AAAA,EAElC;AAAA,EACA;AAAA,EACA,4BAA4B,IAAA;AAAA,EAEtC,YAAYC,GAAkBC,GAA8C;AAC3E,IAAGA,IACF,KAAK,OAAO,IAAIF,EAAWC,GAAMC,CAAM,IAEvC,KAAK,OAAO,IAAIF,EAAWC,CAAI,GAEhC,KAAK,OAAOA,GAEZ,KAAK,KAAK,WAAW,CAACE,MAA6B;AAClD,UAAIC,IAAU,QAAQ,KAAKD,GAAa,CAAC;AACzC,UAAGC,GAAS;AACX,YAAIC,IAAO,KAAK,MAAM,IAAID,CAAO;AACjC,QAAIC,MACHA,IAAO,KAAK,SAASD,CAAO,IAG1BC,MACFA,EAAK,KAAA,GACL,KAAK,MAAM,OAAOD,CAAO;AAAA,MAE3B;AAAA,IACD;AAAA,EACD;AAAA,EAEA,IAAI,SAAS;AACZ,WAAO,KAAK,KAAK;AAAA,EAClB;AAAA,EAEA,QAAQ;AACP,SAAK,KAAK,MAAA,GACV,KAAK,MAAM,MAAA;AAAA,EACZ;AAAA,EAEA,OAAOC,GAAS;AACf,SAAK,KAAK,OAAOA,EAAK,OAAO,GAC7B,KAAK,MAAM,IAAIA,EAAK,SAASA,CAAI;AAAA,EAClC;AAAA,EACA,OAAOA,GAAS;AACf,gBAAK,MAAM,OAAOA,EAAK,OAAO,GACvB,KAAK,KAAK,YAAYA,EAAK,OAAO;AAAA,EAC1C;AAAA,EAEA,aAAaD,GAAgC;AAC5C,QAAIC,IAAO,KAAK,MAAM,IAAID,CAAO;AACjC,WAAIC,MACHA,IAAO,KAAK,SAASD,CAAO,GACzBC,KACF,KAAK,MAAM,IAAID,GAASC,CAAI,IAIvBA;AAAA,EACR;AAAA,EAGA,EAAE,OAAO,QAAQ,IAAI;AACpB,QAAIC,IAAW,KAAK,KAAK,OAAO,QAAQ,EAAA;AAExC,aAAQ,EAAE,MAAMH,GAAa,eAAAI,EAAA,KAAmBD,GAAU;AACzD,UAAIF,IAAU,QAAQ,KAAKD,GAAa,CAAC;AACzC,UAAG,CAACC;AACH;AAGD,UAAIC,IAAO,KAAK,MAAM,IAAID,CAAO;AACjC,MAAIC,MACHA,IAAO,KAAK,SAASD,CAAO,GACzBC,KACF,KAAK,MAAM,IAAID,GAASC,CAAI,IAI3BA,MACF,MAAM;AAAA,QACL,MAAAA;AAAA,QACA,eAAAE;AAAA,MAAA;AAAA,IAGH;AAAA,EACD;AAAA,EACA,QAAQC,GAA6BC,GAA+B;AACnE,aAAQ,EAAE,MAAAJ,EAAA,KAAU;AACnB,OAAG,CAACI,KAAUA,EAAOJ,CAAI,MACxBG,EAASH,CAAI;AAAA,EAGhB;AAAA,EAEA,KAAKG,GAA+C;AACnD,aAAQ,EAAE,MAAAH,EAAA,KAAU;AACnB,UAAGG,EAASH,CAAI;AACf,eAAOA;AAAA,EAGV;AAAA,EACA,OAAOG,GAA4C;AAClD,QAAIE,IAAQ,CAAA;AACZ,aAAQ,EAAE,MAAAL,EAAA,KAAU;AACnB,MAAGG,EAASH,CAAI,KACfK,EAAM,KAAKL,CAAI;AAIjB,WAAOK;AAAA,EACR;AAAA,EACA,IAAOF,GAAoC;AAC1C,UAAMG,IAAkB,CAAA;AACxB,aAAQ,EAAE,MAAAN,EAAA,KAAU;AACnB,MAAAM,EAAM,KAAKH,EAASH,CAAI,CAAC;AAG1B,WAAOM;AAAA,EACR;AAAA,EAEA,kBAAkB;AACjB,WAAO,KAAK,KAAK,gBAAA;AAAA,EAClB;AAAA,EAEA,OAAO;AACN,SAAK,KAAK,KAAA,GACV,KAAK,MAAM,MAAA;AAAA,EACZ;AACD;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pow2.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed-array-constructor.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed-array.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import n from "./allocated-memory.js";
|
|
2
|
+
import { getPointer as o } from "./utils/pointer.js";
|
|
3
|
+
class g {
|
|
4
|
+
memory;
|
|
5
|
+
pointers = [];
|
|
6
|
+
recycledIndexes = [];
|
|
7
|
+
totalLength = 0;
|
|
8
|
+
maxChunkSize = 100;
|
|
9
|
+
dataLength = 1;
|
|
10
|
+
byteMultipler = 1;
|
|
11
|
+
typeConstructor;
|
|
12
|
+
fullDataBlocks = {};
|
|
13
|
+
get length() {
|
|
14
|
+
return this.totalLength - this.recycledIndexes.length;
|
|
15
|
+
}
|
|
16
|
+
get bufferLength() {
|
|
17
|
+
return this.maxChunkSize * this.pointers.length;
|
|
18
|
+
}
|
|
19
|
+
constructor(t, e) {
|
|
20
|
+
this.memory = t;
|
|
21
|
+
const a = e?.dataLength ?? 1, h = e?.maxChunkSize ?? 100;
|
|
22
|
+
this.dataLength = a, this.maxChunkSize = h, this.typeConstructor = e.type, this.typeConstructor === Float64Array && (this.byteMultipler = 2);
|
|
23
|
+
let i = t.allocUI32(h * a * this.byteMultipler);
|
|
24
|
+
this.pointers.push(i.pointer);
|
|
25
|
+
let s = new this.typeConstructor(i.data.buffer, i.bufferByteOffset, this.dataLength * this.maxChunkSize);
|
|
26
|
+
this.fullDataBlocks[0] = s;
|
|
27
|
+
}
|
|
28
|
+
at(t) {
|
|
29
|
+
let e = this.fullDataBlocks[Math.floor(t / this.maxChunkSize)];
|
|
30
|
+
return this.getDataBlock(e, t % this.maxChunkSize);
|
|
31
|
+
}
|
|
32
|
+
get(t, e = 0) {
|
|
33
|
+
const a = this.dataLength;
|
|
34
|
+
if (e >= a)
|
|
35
|
+
throw new Error(`${e} is out of dataLength bounds ${a}`);
|
|
36
|
+
return this.fullDataBlocks[Math.floor(t / this.maxChunkSize)][t % this.maxChunkSize * a + e];
|
|
37
|
+
}
|
|
38
|
+
push(t) {
|
|
39
|
+
const e = typeof t == "number";
|
|
40
|
+
if (!e && t.length > this.dataLength)
|
|
41
|
+
throw new Error(`Can't insert ${t.length} array into shared list of ${this.dataLength} dataLength`);
|
|
42
|
+
let a = this.recycledIndexes.pop();
|
|
43
|
+
a === void 0 && (a = this.totalLength++);
|
|
44
|
+
let h = Math.floor(a / this.maxChunkSize);
|
|
45
|
+
if (h >= this.pointers.length) {
|
|
46
|
+
let l = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);
|
|
47
|
+
this.pointers.push(l.pointer);
|
|
48
|
+
let u = new this.typeConstructor(l.data.buffer, l.bufferByteOffset, this.dataLength * this.maxChunkSize);
|
|
49
|
+
this.fullDataBlocks[h] = u;
|
|
50
|
+
}
|
|
51
|
+
let i = this.fullDataBlocks[h], s = a % this.maxChunkSize, r = this.dataLength * s;
|
|
52
|
+
return e ? i[r] = t : i.set(t, r), a;
|
|
53
|
+
}
|
|
54
|
+
deleteIndex(t) {
|
|
55
|
+
this.recycledIndexes.push(t);
|
|
56
|
+
}
|
|
57
|
+
clear() {
|
|
58
|
+
this.totalLength = 0, this.recycledIndexes = [];
|
|
59
|
+
}
|
|
60
|
+
*[Symbol.iterator]() {
|
|
61
|
+
const t = {};
|
|
62
|
+
for (let h of this.recycledIndexes)
|
|
63
|
+
t[h] = !0;
|
|
64
|
+
let e = this.getFullDataBlock(0), a = 0;
|
|
65
|
+
for (let h = 0; h < this.totalLength; h++)
|
|
66
|
+
if (!t[h]) {
|
|
67
|
+
let i = Math.floor(h / this.maxChunkSize);
|
|
68
|
+
i !== a && (e = this.getFullDataBlock(h), a = i), yield this.getDataBlock(e, h % this.maxChunkSize);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
getFullDataBlock(t) {
|
|
72
|
+
let e = Math.floor(t / this.maxChunkSize), a = this.fullDataBlocks[e];
|
|
73
|
+
if (a)
|
|
74
|
+
return a;
|
|
75
|
+
if (e >= this.pointers.length) {
|
|
76
|
+
let s = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);
|
|
77
|
+
this.pointers.push(s.pointer);
|
|
78
|
+
}
|
|
79
|
+
let h = new n(this.memory, o(this.pointers[e])), i = new this.typeConstructor(h.data.buffer, h.bufferByteOffset, this.dataLength * this.maxChunkSize);
|
|
80
|
+
return this.fullDataBlocks[e] = i, i;
|
|
81
|
+
}
|
|
82
|
+
getDataBlock(t, e) {
|
|
83
|
+
const a = e * this.dataLength;
|
|
84
|
+
return t.subarray(a, a + this.dataLength);
|
|
85
|
+
}
|
|
86
|
+
free() {
|
|
87
|
+
for (let t of this.pointers)
|
|
88
|
+
new n(this.memory, o(t)).free();
|
|
89
|
+
this.pointers = [], this.recycledIndexes = [], this.totalLength = 0, this.fullDataBlocks = {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
g as default
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=local-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-pool.js","sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends Uint32Array | Int32Array | Float32Array | Float64Array = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\t// @ts-expect-error\n\t\tif(this.typeConstructor === Float64Array) {\n\t\t\tthis.byteMultipler = 2;\n\t\t}\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize) as T;\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tconst dataLength = this.dataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\t\t\n\t\t\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize) as T;\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex];\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex]));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize) as T;\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends Uint32Array | Int32Array | Float32Array | Float64Array> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"names":["LocalPool","memory","config","dataLength","maxLength","firstArray","data","index","dataBlock","dataIndex","values","isSingleNumber","newIndex","pointerIndex","newArray","blockIndex","startIndex","recycledValues","value","dataBlockIndex","i","newDataBlockIndex","cachedDataBlock","array","AllocatedMemory","getPointer","rawData","start","pointer"],"mappings":"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"}
|