^ ^ ^ ^
=(o.o)= ~byakuren =(o.o)=
m m m m
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:
- Compute-intensive operations - When you have many FLOPs per element
- Large arrays - Parallel overhead is amortized over millions of elements
- Nested parallelism - Operations that J executes sequentially
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
- Inline Futhark code - Write Futhark as string literals or multiline 0 : 0 blocks
- Automatic compilation - First use compiles; subsequent uses are cached
- Persistent context - Futhark context created once, reused for all calls
- Content-based caching - SHA-256 hash ensures identical code shares libraries
- Scalar and array inputs - Functions can take scalar or array inputs (v1.6.0)
- Fixed-size dimensions - Support for [][32]i64 style types (v1.7.0)
- Multi-dimensional arrays - 1D, 2D, 3D arrays all work seamlessly
- Dynamic output sizes - Output can differ in shape from input
- Helper functions - Use def to define reusable helper functions (v1.7.0)
- Dyadic verbs - Two-input functions work naturally as x verb y
- Clear error messages - Full Futhark compiler output on failures (v1.6.0)
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
- J 9.x with FFI support
- Futhark compiler (install guide)
- GCC for compiling generated C code
- POSIX system: Linux, FreeBSD, or macOS
How It Works
- Hash - Source code is hashed with SHA-256
- Cache check - Return cached .so if exists
- Compile - futhark multicore --library generates C code
- Build - GCC compiles to shared library
- Initialize - Futhark context created with thread pool
- 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
- Test suite output - 123/123 tests passing
- Demo output - Side-by-side J vs Futhark comparison
- Compute benchmark - Logistic map, complex math, Mandelbrot
- StdDev benchmark - Standard deviation performance
- Dot product benchmark - Memory-bound operation
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
Links
- Futhark Language
- J Programming Language
- Futhark User’s Guide