@pid7/ashwa
v1.0.1
Published
Hardware accelerated routines for single substring search
Maintainers
Readme
Ashwa 🐎
Hardware accelerated routines for single substring search
💡 TIP:
@pid7/ashwaruns seamlessly across Node.js, Bun, Deno, and modern browser / WebWorker environments.
ℹ️ NOTE: The optimal execution backend (native N-API SIMD bindings for Node / Bun / Deno or WebAssembly SIMD for browsers) is automatically detected and selected at runtime with zero manual configuration.
Index
Supported Platforms
| Architecture | Target Platform | Target ISA | Fallback |
|:----------------|:---------------------------------------|:-------------------------------------|:------------|
| x86_64 | Linux, macOS, Windows | AVX-512BW, AVX2, SSE4.2, SSSE3, SSE2 | 64-bit SWAR |
| AArch64 (ARM64) | Apple Silicon, Linux ARM64 | 128-bit ARM NEON | 64-bit SWAR |
| WebAssembly | Browsers, WebWorkers, Node.js (wasm32) | WASM SIMD128 (simd128) | 32-bit SWAR |
Installation
Install @pid7/ashwa using your preferred package manager:
npm install @pid7/ashwaUsage
Node
Native N-API bindings execute synchronously with zero overhead and full hardware SIMD acceleration,
import { searchOne, searchTwo } from '@pid7/ashwa';
const haystack = new TextEncoder().encode("The quick brown fox jumps over the lazy dog");
const indexOne = searchOne(haystack, "f".charCodeAt(0));
console.log(`Found 'f' at byte index: ${indexOne}`); // 16Browser
In browser and WebWorker runtimes, WebAssembly SIMD (simd128) is used. The WASM module automatically
initializes asynchronously on the first search call, or can be pre-warmed during application startup:
import { init, searchOne, searchTwo } from '@pid7/ashwa';
// Optional pre-init
await init();
const haystack = new TextEncoder().encode("The quick brown fox jumps over the lazy dog");
const index = await searchOne(haystack, "f".charCodeAt(0));
console.log(`Found 'f' at byte index: ${index}`); // 16API Reference
searchOne(haystack, needle)
function searchOne(
haystack: Uint8Array,
needle: number,
): number | null | Promise<number | null>;Searches for the first occurrence of needle (byte value 0–255) within haystack.
- Parameters:
haystack:Uint8Array/Buffer— The byte sequence to search.needle:number— The target byte value (0–255) to locate.
- Returns:
- In Node.js / Bun / Deno:
number | null(synchronous 0-based byte index, ornullif not found). - In Browser / WebWorker:
Promise<number | null>(resolves to 0-based byte index, ornullif not found).
- In Node.js / Bun / Deno:
searchTwo(haystack, needle)
function searchTwo(
haystack: Uint8Array,
needle: Uint8Array | [number, number] | number[],
): number | null | Promise<number | null>;Searches for the first occurrence of a two-byte needle within haystack.
- Parameters:
haystack:Uint8Array/Buffer— The byte sequence to search.needle:Uint8Array | [number, number] | number[]— A 2-byte sequence to locate.
- Returns:
- In Node.js / Bun / Deno:
number | null(synchronous 0-based byte index, ornullif not found). - In Browser / WebWorker:
Promise<number | null>(resolves to 0-based byte index, ornullif not found).
- In Node.js / Bun / Deno:
searchThree(haystack, needle)
function searchThree(
haystack: Uint8Array,
needle: Uint8Array | [number, number, number] | number[],
): number | null | Promise<number | null>;Searches for the first occurrence of a three-byte needle within haystack.
- Parameters:
haystack:Uint8Array/Buffer— The byte sequence to search.needle:Uint8Array | [number, number, number] | number[]— A 3-byte sequence to locate.
- Returns:
- In Node.js / Bun / Deno:
number | null(synchronous 0-based byte index, ornullif not found). - In Browser / WebWorker:
Promise<number | null>(resolves to 0-based byte index, ornullif not found).
- In Node.js / Bun / Deno:
searchN(haystack, needle)
function searchN(
haystack: Uint8Array,
needle: Uint8Array | number[],
): number | null | Promise<number | null>;Searches for the first occurrence of an arbitrary byte sequence needle within haystack.
- Parameters:
haystack:Uint8Array/Buffer— The byte sequence to search.needle:Uint8Array | number[]— An arbitrary byte sequence to locate.
- Returns:
- In Node.js / Bun / Deno:
number | null(synchronous 0-based byte index, ornullif not found). - In Browser / WebWorker:
Promise<number | null>(resolves to 0-based byte index, ornullif not found).
- In Node.js / Bun / Deno:
init(moduleOrPath?)
function init(moduleOrPath?: any): Promise<void>;Asynchronously pre-initializes the WebAssembly module backend for Browser / WebWorker environments. In native
Node.js environments (isNative === true), this is a no-op that resolves immediately.
- Parameters:
moduleOrPath(optional):WebAssembly.Module | Response | ArrayBuffer | string— Custom WASM source or URL.
initSync(bytesOrModule?)
function initSync(bytesOrModule?: any): void;Synchronously initializes the WebAssembly module backend with pre-compiled bytes or a WebAssembly.Module.
In native Node.js environments, this is a no-op.
- Parameters:
bytesOrModule(optional):ArrayBuffer | Uint8Array | WebAssembly.Module— Pre-loaded WASM binary or module.
isNative
const isNative: boolean;Boolean flag indicating whether @pid7/ashwa is executing via native N-API bindings (true) or
WebAssembly SIMD (false).
Benchmarks
Benchmarks are evaluated across dedicated AWS EC2 hardware environments on Node.js
v22.23.2,
- x86_64 (x64)
- Instance: Intel(R) Xeon(R) Platinum 8488C (8C/16T)
- ISA: AVX-512BW · WASM SIMD128
- Cache: L1d: 384 KiB · L1i: 256 KiB · L2: 16 MiB · L3: 105 MiB
- STREAM Triad (Read + Write): 25.76 GiB/s
- Max Single-Core Read Bandwidth: ~12.5 GiB/s
- AArch64 (arm64)
- Instance: AWS Graviton3 ARM Neoverse-V1 (16C/16T)
- ISA: NEON
- Cache: L1d: 1 MiB · L1i: 1 MiB · L2: 16 MiB · L3: 32 MiB
- STREAM Triad (Read + Write): 75.48 GiB/s
- Max Single-Core Read Bandwidth: ~25.5 GiB/s
All benchmarks execute while pinned to a single physical core (mostly to 0th core). For out-of-cache RAM-bound payloads (>256 MiB), every search routine fully saturates the hardwares maximum single-core memory bandwidth ceiling (~12 GiB/s on x64, ~25 GiB/s on arm64). Execution is 100% memory-bandwidth bound rather than compute-bound searching data as fast as the physical memory bus can deliver bytes into the CPU registers.
searchOne
Native Node.js (V8 N-API)
| Level | Payload | Latency (x64) | Latency (arm64) | Throughput (x64) | Throughput (arm64) | |:-----------|:----------|:--------------|:----------------|:-----------------|:-------------------| | L1 Cache | 32 KiB | 259.88 ns | 730.67 ns | 117.43 GiB/s | 41.77 GiB/s | | L2 Cache | 512 KiB | 6.90 µs | 11.15 µs | 70.76 GiB/s | 43.78 GiB/s | | L3 Cache | 16 MiB | 488.06 µs | 376.89 µs | 32.01 GiB/s | 41.46 GiB/s | | RAM | 256 MiB | 20.19 ms | 9.84 ms | 12.38 GiB/s | 25.40 GiB/s | | RAM | 512 MiB | 41.79 ms | 19.60 ms | 11.96 GiB/s | 25.52 GiB/s | | RAM | 1 GiB | 83.68 ms | 39.11 ms | 11.95 GiB/s | 25.57 GiB/s |
WebAssembly (WASM SIMD128)
| Level | Payload | Latency (x64) | Throughput (x64) | |:-----------|:----------|:--------------|:-----------------| | L1 Cache | 32 KiB | 1.28 µs | 23.88 GiB/s | | L2 Cache | 512 KiB | 20.77 µs | 23.51 GiB/s | | L3 Cache | 16 MiB | 1.53 ms | 10.22 GiB/s | | RAM | 256 MiB | 48.19 ms | 5.19 GiB/s | | RAM | 512 MiB | 96.64 ms | 5.17 GiB/s | | RAM | 1 GiB | 192.52 ms | 5.19 GiB/s |
searchTwo
Native Node.js (V8 N-API)
| Level | Payload | Latency (x64) | Latency (arm64) | Throughput (x64) | Throughput (arm64) | |:-----------|:----------|:--------------|:----------------|:-----------------|:-------------------| | L1 Cache | 32 KiB | 563.60 ns | 1.57 µs | 54.15 GiB/s | 19.38 GiB/s | | L2 Cache | 512 KiB | 12.14 µs | 23.93 µs | 40.22 GiB/s | 20.40 GiB/s | | L3 Cache | 16 MiB | 558.19 µs | 771.07 µs | 27.99 GiB/s | 20.26 GiB/s | | RAM | 256 MiB | 20.05 ms | 13.03 ms | 12.47 GiB/s | 19.18 GiB/s | | RAM | 512 MiB | 41.59 ms | 26.07 ms | 12.02 GiB/s | 19.18 GiB/s | | RAM | 1 GiB | 83.70 ms | 52.08 ms | 11.95 GiB/s | 19.20 GiB/s |
WebAssembly (WASM SIMD128)
| Level | Payload | Latency (x64) | Throughput (x64) | |:-----------|:----------|:--------------|:-----------------| | L1 Cache | 32 KiB | 1.87 µs | 16.31 GiB/s | | L2 Cache | 512 KiB | 28.72 µs | 17.00 GiB/s | | L3 Cache | 16 MiB | 1.80 ms | 8.69 GiB/s | | RAM | 256 MiB | 53.44 ms | 4.68 GiB/s | | RAM | 512 MiB | 106.71 ms | 4.69 GiB/s | | RAM | 1 GiB | 213.08 ms | 4.69 GiB/s |
searchThree
Native Node.js (V8 N-API)
| Level | Payload | Latency (x64) | Latency (arm64) | Throughput (x64) | Throughput (arm64) | |:-----------|:----------|:--------------|:----------------|:-----------------|:-------------------| | L1 Cache | 32 KiB | 600.64 ns | 2.26 µs | 50.81 GiB/s | 13.50 GiB/s | | L2 Cache | 512 KiB | 9.56 µs | 34.96 µs | 51.08 GiB/s | 13.97 GiB/s | | L3 Cache | 16 MiB | 487.42 µs | 1.13 ms | 32.06 GiB/s | 13.86 GiB/s | | RAM | 256 MiB | 18.66 ms | 17.96 ms | 13.40 GiB/s | 13.92 GiB/s | | RAM | 512 MiB | 44.00 ms | 35.90 ms | 11.36 GiB/s | 13.93 GiB/s | | RAM | 1 GiB | 87.98 ms | 71.86 ms | 11.37 GiB/s | 13.92 GiB/s |
WebAssembly (WASM SIMD128)
| Level | Payload | Latency (x64) | Throughput (x64) | |:-----------|:----------|:--------------|:-----------------| | L1 Cache | 32 KiB | 2.30 µs | 13.26 GiB/s | | L2 Cache | 512 KiB | 34.11 µs | 14.31 GiB/s | | L3 Cache | 16 MiB | 1.82 ms | 8.58 GiB/s | | RAM | 256 MiB | 54.99 ms | 4.55 GiB/s | | RAM | 512 MiB | 114.54 ms | 4.37 GiB/s | | RAM | 1 GiB | 227.27 ms | 4.40 GiB/s |
searchN (where len(N) = 8)
Native Node.js (V8 N-API)
| Level | Payload | Latency (x64) | Latency (arm64) | Throughput (x64) | Throughput (arm64) | |:-----------|:----------|:--------------|:----------------|:-----------------|:-------------------| | L1 Cache | 32 KiB | 940.14 ns | 2.62 µs | 32.46 GiB/s | 11.63 GiB/s | | L2 Cache | 512 KiB | 15.53 µs | 38.68 µs | 31.44 GiB/s | 12.62 GiB/s | | L3 Cache | 16 MiB | 590.67 µs | 1.23 ms | 26.45 GiB/s | 12.69 GiB/s | | RAM | 256 MiB | 21.28 ms | 19.64 ms | 11.75 GiB/s | 12.73 GiB/s | | RAM | 512 MiB | 44.52 ms | 39.02 ms | 11.23 GiB/s | 12.82 GiB/s | | RAM | 1 GiB | 89.46 ms | 78.20 ms | 11.18 GiB/s | 12.79 GiB/s |
WebAssembly (WASM SIMD128)
| Level | Payload | Latency (x64) | Throughput (x64) | |:-----------|:----------|:--------------|:-----------------| | L1 Cache | 32 KiB | 2.64 µs | 11.54 GiB/s | | L2 Cache | 512 KiB | 39.46 µs | 12.37 GiB/s | | L3 Cache | 16 MiB | 2.00 ms | 7.82 GiB/s | | RAM | 256 MiB | 56.92 ms | 4.39 GiB/s | | RAM | 512 MiB | 114.60 ms | 4.36 GiB/s | | RAM | 1 GiB | 228.40 ms | 4.38 GiB/s |
