Futhark-J Bridge

High-performance parallel array computing for J via Futhark

Write Futhark code inline in your J programs and get automatic parallel execution on all CPU cores.

Download: futhark.ijs (v1.7.0)

What is this?

The Futhark-J Bridge lets you write Futhark code directly in your J source files. Futhark is a purely functional data-parallel programming language that compiles to highly efficient parallel code. This bridge handles compilation, caching, and FFI calls automatically.

NB. Define a Futhark function inline
double =: 'entry f (xs: []f64) : []f64 = map (*2) xs' fut

NB. Use it like any J verb
double 1 2 3 4 5
   2 4 6 8 10

Why use Futhark from J?

J is already excellent at array programming, but some workloads benefit from parallel execution across multiple CPU cores. Futhark excels at:

Performance Results

The library includes benchmarks comparing native J to Futhark. Here are some highlights:

Workload Max Speedup Notes
Mandelbrot escape 1525x Variable iteration count per element
Logistic map (1000 iters) 14x High compute per element
Standard deviation 2x Multiple passes over data
Dot product 0.3x Memory-bound; J wins

Key insight: Futhark shines when computation dominates memory access. For simple operations like dot product, native J is faster due to FFI overhead.

Quick Start

NB. Load the library
load 'futhark.ijs'

NB. Array transformations
square =: 'entry f (xs: []f64) : []f64 = map (\x -> x * x) xs' fut
square 1 2 3 4 5
   1 4 9 16 25

NB. Reductions to scalar
total =: 'entry f (xs: []f64) : f64 = reduce (+) 0 xs' fut
total 1 2 3 4 5
   15

NB. Two-input (dyadic) functions
dot =: 'entry f (xs: []f64) (ys: []f64) : f64 = reduce (+) 0 (map2 (*) xs ys)' fut
(1 2 3) dot (4 5 6)
   32

NB. Scalar input functions (v1.6.0) - use 0 : 0 for multiline Futhark
factors_src =: 0 : 0
  entry f (n: i64) : []i64 =
    let (_, _, result) =
      loop (x, i, acc) = (n, 2i64, ([] : []i64))
      while x > 1 do
        if x % i == 0
        then (x / i, i, acc ++ [i])
        else (x, i + 1, acc)
    in result
)
factors =: factors_src fut

factors 12
   2 2 3
factors 100
   2 2 5 5

NB. Fixed-size array dimensions (v1.7.0)
NB. Output has fixed inner dimension [3]
triple =: 'entry f (ns: []i64) : [][3]i64 = map (\n -> [n, n*2, n*3]) ns' fut
triple 1 2 3 4 5
   1  2  3
   2  4  6
   3  6  9
   4  8 12
   5 10 15

Features

Futhark Syntax Primer

Futhark entry points follow this pattern:

entry <name> (<param>: <type>) : <return_type> = <body>

Common operations:

Futhark J Equivalent Description
map (*2) xs 2 * xs Element-wise transform
reduce (+) 0 xs +/ xs Fold to scalar
scan (+) 0 xs +/\ xs Running sum
map2 (*) xs ys xs * ys Element-wise binary op
transpose xs \|: xs Matrix transpose
flatten xs , xs Ravel to 1D

Requirements

How It Works

  1. Hash - Source code is hashed with SHA-256
  2. Cache check - Return cached .so if exists
  3. Compile - futhark multicore --library generates C code
  4. Build - GCC compiles to shared library
  5. Initialize - Futhark context created with thread pool
  6. Execute - J calls library via FFI, passes arrays, retrieves results

The context is created at definition time and persists, so there’s no “warmup” penalty on first execution.

Files

File Description
futhark.ijs Main library - load this
futhark_test.ijs Test suite (123 tests)
futhark_demo.ijs Interactive demonstration
compute_bench.ijs Compute-intensive benchmarks
stddev_bench.ijs Standard deviation benchmark
dot_product_bench.ijs Dot product benchmark
README.md User documentation
DOCUMENTATION.md Technical deep-dive

Sample Outputs

Example: Statistical Functions

NB. Variance
f_variance =: 'entry f (xs: []f64) : f64 = let mean = reduce (+) 0 xs / f64.i64 (length xs) in let sq_diffs = map (\x -> (x - mean) ** 2) xs in reduce (+) 0 sq_diffs / f64.i64 (length xs)' fut

NB. Standard deviation
f_stddev =: 'entry f (xs: []f64) : f64 = let mean = reduce (+) 0 xs / f64.i64 (length xs) in let sq_diffs = map (\x -> (x - mean) ** 2) xs in f64.sqrt (reduce (+) 0 sq_diffs / f64.i64 (length xs))' fut

NB. Softmax
f_softmax =: 'entry f (xs: []f64) : []f64 = let max_val = reduce f64.max f64.lowest xs in let exps = map (\x -> f64.exp (x - max_val)) xs in let sum_exps = reduce (+) 0 exps in map (\e -> e / sum_exps) exps' fut

NB. Z-score normalization
f_zscore =: 'entry f (xs: []f64) : []f64 = let n = length xs in let mean = reduce (+) 0 xs / f64.i64 n in let sq_diffs = map (\x -> (x - mean) ** 2) xs in let std = f64.sqrt (reduce (+) 0 sq_diffs / f64.i64 n) in if std == 0.0 then replicate n 0.0 else map (\x -> (x - mean) / std) xs' fut

Example: Chaining J and Futhark

Data flows seamlessly between J and Futhark:

input =: 100 200 300 400 500
   100 200 300 400 500

step1 =: f_normalize input      NB. Futhark
   0 0.25 0.5 0.75 1

step2 =: 100 * step1            NB. J
   0 25 50 75 100

step3 =: f_square step2         NB. Futhark
   0 625 2500 5625 10000

step4 =: <. step3               NB. J floor
   0 625 2500 5625 10000

step5 =: f_cumsum step4         NB. Futhark
   0 625 3125 8750 18750

Version History

Version Changes
1.7.0 Fixed-size array dimensions ([][32]i64), correct parsing with def helper functions, 123 tests
1.6.0 Scalar input support, improved error reporting, 96 tests
1.5.0 SHA-256 hashing (128!:6) for cache keys
1.4.0 Persistent Futhark context (no warmup needed)
1.3.0 Dyadic verb support (two inputs)
1.2.0 Dynamic n-dimensional array support
1.1.0 2D array support
1.0.0 Initial release

License

MIT License