Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

parallelMap

Signature: parallelMap<T, U>(data: T[], fn: (item: T) => U): U[] — imported from perry/thread.

Processes every element of an array in parallel across all available CPU cores. Returns a new array with the results in the same order as the input.

Basic Usage

function parallelMapBasic(): void {
    const numbers = [1, 2, 3, 4, 5, 6, 7, 8]
    const doubled = parallelMap(numbers, (x: number) => x * 2)
    // [2, 4, 6, 8, 10, 12, 14, 16]
    console.log(`parallel-map-basic len=${doubled.length}`)
}

How It Works

Input: [a, b, c, d, e, f, g, h]     (8 elements, 4 CPU cores)

  Core 1: [a, b] → map → [a', b']
  Core 2: [c, d] → map → [c', d']
  Core 3: [e, f] → map → [e', f']
  Core 4: [g, h] → map → [g', h']

Output: [a', b', c', d', e', f', g', h']   (same order as input)

Perry automatically detects the number of CPU cores and splits the array into equal chunks. Elements within each chunk are processed sequentially; chunks run concurrently across cores.

Capturing Variables

The mapping function can reference variables from the outer scope. Captured values are deep-copied to each worker thread automatically:

function parallelMapCapture(): void {
    const prices = [100, 200, 300]
    const exchangeRate = 1.12

    const converted = parallelMap(prices, (price: number) => {
        // exchangeRate is captured and copied to each thread
        return price * exchangeRate
    })

    console.log(`parallel-map-capture len=${converted.length}`)
}

What Can Be Captured

TypeSupportedTransfer
NumbersYesZero-cost (64-bit copy)
BooleansYesZero-cost
StringsYesByte copy
ArraysYesDeep copy
ObjectsYesDeep copy
const variablesYesCopied
let/var variablesOnly if not reassignedCopied

What Cannot Be Captured

Mutable variables — variables that are reassigned anywhere in the enclosing scope — are rejected at compile time:

// Reject example — Perry rejects this at compile time:

let total = 0;

// COMPILE ERROR: Cannot capture mutable variable 'total'
parallelMap(data, (item) => {
    total += item;   // Would be a data race
    return item;
});

Instead, return values and reduce:

function parallelMapReduce(): void {
    const data = [1, 2, 3, 4, 5, 6, 7, 8]
    const results = parallelMap(data, (item: number) => item * 2)
    const total = results.reduce((sum: number, x: number) => sum + x, 0)
    console.log(`parallel-map-reduce total=${total}`)
}

Performance

When to Use parallelMap

Use parallelMap when the computation per element is significantly heavier than the cost of copying the element across threads.

Good candidates (CPU-bound work per element):

function parallelMapGoodCandidates(): void {
    const data = [1.0, 2.0, 3.0, 4.0]
    const documents = ["alpha beta", "gamma delta", "epsilon"]
    const inputs = ["a", "bb", "ccc"]

    // Heavy math
    const out1 = parallelMap(data, (x: number) => {
        let acc = x
        for (let i = 0; i < 1_000; i++) acc = Math.sqrt(acc * acc + i)
        return acc
    })

    // String processing on large strings
    const out2 = parallelMap(documents, (doc: string) => {
        const words = doc.split(" ")
        return { count: words.length, first: words[0] }
    })

    // Cryptographic operations
    const out3 = parallelMap(inputs, (input: string) => {
        let h = 0
        for (let i = 0; i < input.length; i++) h = (h * 31 + input.charCodeAt(i)) >>> 0
        return h
    })

    console.log(`parallel-map-good-candidates ${out1.length} ${out2.length} ${out3.length}`)
}

Poor candidates (trivial work per element):

function parallelMapPoorCandidate(): void {
    const numbers = [1, 2, 3, 4, 5]

    // Too simple — threading overhead outweighs the gain
    const a = parallelMap(numbers, (x: number) => x + 1)

    // For trivial operations, use regular map
    const result = numbers.map((x: number) => x + 1)

    console.log(`parallel-map-poor-candidate ${a.length} ${result.length}`)
}

Small Array Optimization

For arrays with fewer elements than CPU cores, Perry skips threading entirely and processes elements inline on the main thread. There’s zero overhead for small inputs.

Numeric Fast Path

When elements are pure numbers (no strings, objects, or arrays), Perry transfers them between threads at virtually zero cost — just 64-bit value copies with no serialization.

Examples

Matrix Row Processing

function parallelMapMatrix(): void {
    // Process each row of a matrix independently
    const rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    const rowSums = parallelMap(rows, (row: number[]) => {
        let sum = 0
        for (const val of row) sum += val
        return sum
    })
    // [6, 15, 24]
    console.log(`parallel-map-matrix sums=${rowSums[0]},${rowSums[1]},${rowSums[2]}`)
}

Batch Validation

function parallelMapValidation(): void {
    const users = [
        { name: "Alice", email: "alice@example.com" },
        { name: "Bob", email: "invalid" },
        { name: "Charlie", email: "charlie@example.com" },
    ]

    const validationResults = parallelMap(users, (user: { name: string; email: string }) => {
        const emailValid = user.email.includes("@") && user.email.includes(".")
        const nameValid = user.name.length > 0 && user.name.length < 100
        return { name: user.name, valid: emailValid && nameValid }
    })

    console.log(`parallel-map-validation len=${validationResults.length}`)
}

Financial Calculations

function parallelMapMonteCarlo(): void {
    const portfolios = [
        { id: 1, base: 100 },
        { id: 2, base: 200 },
        { id: 3, base: 150 },
    ] // thousands of portfolios

    // Monte Carlo simulation across all cores
    const riskScores = parallelMap(portfolios, (portfolio: { id: number; base: number }) => {
        let totalRisk = 0
        for (let sim = 0; sim < 1000; sim++) {
            // simulateReturns stand-in: deterministic pseudo-random walk.
            let s = portfolio.base + sim
            s = ((s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff
            totalRisk += s
        }
        return totalRisk / 1000
    })

    console.log(`parallel-map-monte-carlo len=${riskScores.length}`)
}