^ ^ ^ ^ =(o.o)= ~byakuren =(o.o)= m m m m
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)
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 10J is already excellent at array programming, but some workloads benefit from parallel execution across multiple CPU cores. Futhark excels at:
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.
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 150 : 0 blocks[][32]i64 style types (v1.7.0)def to define
reusable helper functions (v1.7.0)x verb yFuthark 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 |
.so if
existsfuthark multicore --library
generates C codeThe context is created at definition time and persists, so there’s no “warmup” penalty on first execution.
| 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 |
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' futData 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 | 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 |
MIT License