You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(wasm): cap pthread pool at 2; bump core/gdal to beta.16
Each pthread is a Web Worker with non-trivial memory and startup cost.
PTHREAD_POOL_SIZE is now Math.min(navigator.hardwareConcurrency || 1, 2)
so the heap-duplication and worker-spawn cost stays predictable instead
of scaling with the device's core count. GDAL_NUM_THREADS pinned to '1'
on mt builds so GDAL's own threading doesn't compete with the capped
pool. PTHREAD_POOL_SIZE_STRICT=2 still aborts on overflow.
Docs (docs/api/performance.md, website/docs/api/configuration/performance.md)
updated: defaults table, tunable section, and "common mistakes" item.
Bumps cpp.js, @cpp.js/package-gdal, @cpp.js/package-gdal-wasm to
2.0.0-beta.16.
Copy file name to clipboardExpand all lines: cppjs-packages/cppjs-package-gdal/cppjs-package-gdal-wasm/package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "@cpp.js/package-gdal-wasm",
3
-
"version": "2.0.0-beta.15",
3
+
"version": "2.0.0-beta.16",
4
4
"nativeVersion": "3.12.4",
5
5
"description": "This package provides the precompiled GDAL, built using Cpp.js, for seamless integration into JavaScript, WebAssembly, and React Native projects. It enables advanced geospatial data processing, including raster and vector data manipulation, format conversion, and spatial analysis, ensuring high performance and cross-platform compatibility. Perfect for web, server-side, and mobile geospatial applications.",
Copy file name to clipboardExpand all lines: cppjs-packages/cppjs-package-gdal/cppjs-package-gdal/package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "@cpp.js/package-gdal",
3
-
"version": "2.0.0-beta.15",
3
+
"version": "2.0.0-beta.16",
4
4
"nativeVersion": "3.12.4",
5
5
"description": "This package provides the precompiled GDAL, built using Cpp.js, for seamless integration into JavaScript, WebAssembly, and React Native projects. It enables advanced geospatial data processing, including raster and vector data manipulation, format conversion, and spatial analysis, ensuring high performance and cross-platform compatibility. Perfect for web, server-side, and mobile geospatial applications.",
|`-sMEMORY64=1`| wasm64 only | 64-bit memory | 🔒 Set by target.arch, not flag override |
17
-
|`-pthread` + `-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency`| mt only | Thread pool sized to device CPU at module load| ✅ `PTHREAD_POOL_SIZE` is tunable (see below) |
17
+
|`-pthread` + `-sPTHREAD_POOL_SIZE=Math.min(navigator.hardwareConcurrency \|\| 1, 2)`| mt only | Thread pool capped at 2 workers (1 if `hardwareConcurrency` is unavailable)| ✅ `PTHREAD_POOL_SIZE` is tunable (see below) |
18
18
|`-sPTHREAD_POOL_SIZE_STRICT=2`| mt only | Abort if more pthreads requested than pool (no dynamic growth) | ⚠️ Drop to `1` (warn + grow) or `0` (silent grow) only if your code spawns unbounded threads |
The pool is sized to the device's CPU count at module load — `navigator.hardwareConcurrency`is evaluated as a JS expression by Emscripten at runtime, not baked in at build time. Most apps need no override.
92
+
The default expression is evaluated by Emscripten at module load (not baked at build time):
93
93
94
-
Paired with `-sPTHREAD_POOL_SIZE_STRICT=2`: if your code requests more pthreads than the pool, the runtime **aborts**. No dynamic growth, no main-thread deadlock risk. If your thread count is bounded by `hardwareConcurrency` you're fine; otherwise either bump the pool or relax strictness.
94
+
- Caps the pool at **2 worker threads**, even on 16-core devices.
95
+
- Falls back to **1** if `navigator.hardwareConcurrency` is unavailable (older browsers, Node, edge runtimes).
95
96
96
-
Override only when:
97
+
Why the cap? Each pthread is a Web Worker — non-trivial memory footprint, startup latency, and WASM heap duplication. For typical workloads two workers (main + one) covers parallelism; going past 2 trades memory and load time for diminishing returns. Bump only when you've measured CPU-bound parallelism that scales further.
97
98
98
-
-**Background tasks where you want the main thread responsive** — cap below CPU count:
99
+
Paired with `-sPTHREAD_POOL_SIZE_STRICT=2`: if your code requests more pthreads than the pool, the runtime **aborts**. No dynamic growth, no main-thread deadlock risk. If you bump the pool, also relax strictness or keep your spawn count under the new cap.
100
+
101
+
Override scenarios:
102
+
103
+
-**CPU-bound parallelism that benefits from more workers** (tile-by-tile image processing, parallel decoding) — bump the pool and relax strict mode:
Spawning more than `hardwareConcurrency`doesn't speed things up — context-switching costs dominate.
118
+
Past `hardwareConcurrency`real cores, context-switching costs dominate anyway.
114
119
115
120
### `RESERVED_FUNCTION_POINTERS` (default: 200)
116
121
@@ -207,7 +212,7 @@ Your C++ might not throw, but std::vector / std::string / std::map can. Removing
207
212
208
213
### "I'll bump `PTHREAD_POOL_SIZE` to 32 for max parallelism"
209
214
210
-
Going past `hardwareConcurrency` adds context-switching overhead. The default already matches the device CPU count at runtime — bumping it past that doesn't help. Bump only when your code spawns more concurrent threads than the device has cores AND you've measured a benefit (you'll also need`-sPTHREAD_POOL_SIZE_STRICT=1` so the extra threads don't trip the strict abort).
215
+
Each pthread is a Web Worker with its own WASM instance — bumping pool size to 32 inflates memory and module startup time without a matching speedup. The default caps at 2 deliberately: enough to parallelize the hot path, cheap to spin up. Bump only when measurement shows your workload scales (e.g. embarrassingly parallel image / geo / crypto loops), and pair the bump with`-sPTHREAD_POOL_SIZE_STRICT=1` so spawning more than the new cap doesn't trip the strict abort.
|`-sMEMORY64=1`| wasm64 only | 64-bit memory | 🔒 Set by `target.arch`, not flag override |
17
-
|`-pthread` + `-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency`| mt only | Thread pool sized to device CPU at module load| ✅ `PTHREAD_POOL_SIZE` is tunable (see below) |
17
+
|`-pthread` + `-sPTHREAD_POOL_SIZE=Math.min(navigator.hardwareConcurrency \|\| 1, 2)`| mt only | Thread pool capped at 2 workers (1 if `hardwareConcurrency` is unavailable)| ✅ `PTHREAD_POOL_SIZE` is tunable (see below) |
18
18
|`-sPTHREAD_POOL_SIZE_STRICT=2`| mt only | Abort if more pthreads requested than pool (no dynamic growth) | ⚠️ Drop to `1` (warn + grow) or `0` (silent grow) only if your code spawns unbounded threads |
The pool is sized to the device's CPU count at module load — `navigator.hardwareConcurrency`is evaluated as a JS expression by Emscripten at runtime, not baked in at build time. Most apps need no override.
92
+
The default expression is evaluated by Emscripten at module load (not baked at build time):
93
93
94
-
Paired with `-sPTHREAD_POOL_SIZE_STRICT=2`: if your code requests more pthreads than the pool, the runtime **aborts**. No dynamic growth, no main-thread deadlock risk. If your thread count is bounded by `hardwareConcurrency` you're fine; otherwise either bump the pool or relax strictness.
94
+
- Caps the pool at **2 worker threads**, even on 16-core devices.
95
+
- Falls back to **1** if `navigator.hardwareConcurrency` is unavailable (older browsers, Node, edge runtimes).
95
96
96
-
Override only when:
97
+
Why the cap? Each pthread is a Web Worker — non-trivial memory footprint, startup latency, and WASM heap duplication. For typical workloads two workers (main + one) covers parallelism; going past 2 trades memory and load time for diminishing returns. Bump only when you've measured CPU-bound parallelism that scales further.
97
98
98
-
-**Background tasks where you want the main thread responsive** — cap below CPU count:
99
+
Paired with `-sPTHREAD_POOL_SIZE_STRICT=2`: if your code requests more pthreads than the pool, the runtime **aborts**. No dynamic growth, no main-thread deadlock risk. If you bump the pool, also relax strictness or keep your spawn count under the new cap.
100
+
101
+
Override scenarios:
102
+
103
+
-**CPU-bound parallelism that benefits from more workers** (tile-by-tile image processing, parallel decoding) — bump the pool and relax strict mode:
Spawning more than `hardwareConcurrency`doesn't speed things up — context-switching costs dominate.
118
+
Past `hardwareConcurrency`real cores, context-switching costs dominate anyway.
114
119
115
120
### `RESERVED_FUNCTION_POINTERS` (default: 200)
116
121
@@ -207,7 +212,7 @@ Your C++ might not throw, but `std::vector` / `std::string` / `std::map` can. Re
207
212
208
213
### "I'll bump `PTHREAD_POOL_SIZE` to 32 for max parallelism"
209
214
210
-
Going past `hardwareConcurrency` adds context-switching overhead. The default already matches the device CPU count at runtime — bumping it past that doesn't help. Bump only when your code spawns more concurrent threads than the device has cores AND you've measured a benefit (you'll also need`-sPTHREAD_POOL_SIZE_STRICT=1` so the extra threads don't trip the strict abort).
215
+
Each pthread is a Web Worker with its own WASM instance — bumping pool size to 32 inflates memory and module startup time without a matching speedup. The default caps at 2 deliberately: enough to parallelize the hot path, cheap to spin up. Bump only when measurement shows your workload scales (e.g. embarrassingly parallel image / geo / crypto loops), and pair the bump with`-sPTHREAD_POOL_SIZE_STRICT=1` so spawning more than the new cap doesn't trip the strict abort.
0 commit comments