~/bend-docscommunity

sort.bend relies on unsafe/foreign

raw source on the hub · import 0xeef3a8486d410ea6f25709aac90dbf40/sort.bend as Sort

sort.bend: a parallel stable counting sort by U32 key, with no atomics, and a parallel gather to reorder payloads by its permutation.

import ./sort.bend as Sort Sort.by_key(8n, 256, keys, Sort.Bufs.new()) # (keys, bufs) Sort.Bufs{perm, counts, offs, table} = bufs # perm[j]: the input index of the key the sort puts at j # counts[c], offs[c]: bucket c's size and first slot Sort.gather(~U32, 8n, perm, keys, [0 : U32^d]) # Moved{perm, keys, sorted}

(As in scan.bend: take a call's result apart in a def's parameter.)

k is the bucket count K (0 counts as 1), any U32; a key at or past K sorts as K - 1. The sort is stable: keys of one bucket keep their input order, so the result equals ref.bend's perm, offsets and histogram bit for bit, at every fork depth b. counts and offs hold 2^ceil(log2 K) entries: past K, counts are 0 and offs are n. A call under ! runs its forks on the GPU.

Bufs carries the buffers from call to call (the table of 2^b rows of 2^ceil(log2 K) counts, the counts, the offsets and the permutation). A call keeps a buffer that fits and allocates otherwise; under ! an allocation fills on one lane, so size them once with Bufs.sized outside the ! (see histogram.bend).

Passes: 1. per block of keys, in parallel: the block's counts per bucket into its row of the table (histogram.bend's pass 1); 2. from 2^12 rows up, the table's rows are cut into 2^(b/2) groups of rows (see histogram.bend): a) per (group of rows, group of buckets), in parallel: each column becomes its running sum within the group; b) per group of buckets, in parallel: over the groups' last rows, each bucket's total into counts and offs, and each group's base (the counts of the groups above it) into its last row; then offs becomes its exclusive scan (scan.bend), the buckets' first slots; c) per (group of rows, group of buckets) again: each table entry becomes its bucket's first slot plus the counts of the rows above it (base plus the running sum of the row before it): where the block's keys of that bucket start. Below 2^12 rows, one group of all the rows, and no a): b) sums each column, c) walks it down; 3. per block, in parallel: each key, in order, takes the next slot of its bucket in the block's row, and perm[slot] becomes its index.

Sharing: as in scan.bend, lanes share arrays through Base's @unsafe Array.fork and Array.join; no def here is @unsafe itself, and none may be evaluated in the checker (call them from an IO main).

3 imports
import Base
import ./scan.bend as Scan
import ./histogram.bend as Hist

Types

type Bufs source · line 55 · raw

Type

type Three source · line 71 · raw

Type

type Moved source · line 413 · raw

@-T:Data -> Type

A gather's arrays: the permutation, the source and the destination.

type Sd source · line 416 · raw

@-T:Data -> Type

Definitions

def Bufs.new source · line 59 · raw

Bufs

Empty buffers: the first call allocates what it needs.

def Bufs.sized source · line 63 · raw

@+b:Nat -> @+k:U32 -> @+lg:Nat -> Bufs

Buffers for 2^lg keys at fork depth b and k buckets, allocated now.

def three.join source · line 74 · raw

@l:Three -> @r:Three -> Three

def three.rejoin source · line 79 · raw

@a2:Array<U32> -> @b2:Array<U32> -> @c2:Array<U32> -> @r:Three -> Three

def xs.go source · line 89 · raw

@+n:Nat -> @+at:U32 -> @+step:U32 -> @+s:U32 -> @r:Pair(Array<U32>, U32) -> Pair(Array<U32>, U32)

r holds the table and the entry x read at at: it becomes s, and the n entries after it (each step further on) follow, from s + x. Returns the table and the sum past the last.

def sum.put source · line 99 · raw

@+c:U32 -> @cs:Array<U32> -> @os:Array<U32> -> @r:Pair(Array<U32>, U32) -> Three

def sum.col source · line 105 · raw

@+wr:Bool -> @+at:U32 -> @+step:U32 -> @+nb:Nat -> @t:Array<U32> -> Pair(Array<U32>, U32)

The nb + 1 entries from at, step apart: their sum; when wr, each also becomes the sum of those before it.

def sum.one source · line 115 · raw

@lt:Bool -> @+wr:Bool -> @+c:U32 -> @+o:U32 -> @+step:U32 -> @+nb:Nat -> @p:Three -> Three

Column c, when c < K: over its nb + 1 entries from o + c, step apart (the groups' last rows, or all the rows of a small table), the total into cs[c] and os[c], by sum.col. Else 0 into both.

def sum.go source · line 125 · raw

@+n:Nat -> @+wr:Bool -> @+c:U32 -> @+k:U32 -> @+o:U32 -> @+step:U32 -> @+nb:Nat -> @p:Three -> Three

Columns c to c + n.

def sums source · line 136 · raw

@+d:Nat -> @+wr:Bool -> @+j:U32 -> @+w:Nat -> @+k:U32 -> @+o:U32 -> @+step:U32 -> @+nb:Nat -> @ft:Pair(Array<U32>, Array<U32>) -> @fc:Pair(Array<U32>, Array<U32>) -> @fo:Pair(Array<U32>, Array<U32>) -> Three

Pass 2b's forks: d levels down to groups of 2^w columns under group j, with two handles each to the table, the counts and the offsets. Group j reads (and when wr writes) only its columns of the table and writes only its own entries of counts and offs (j * 2^w to j * 2^w + 2^w - 1).

def base.fin source · line 155 · raw

@+q:U32 -> @+s:U32 -> @t:Array<U32> -> Array<U32>

def base.go source · line 161 · raw

@+n:Nat -> @+at:U32 -> @+step:U32 -> @+s:U32 -> @r:Pair(Array<U32>, U32) -> Array<U32>

r holds the table and x, the entry a row (step) above at, which holds the group's running sum through that row: at becomes s + x, and so up for the n rows above; the group's first row, the next above, becomes s.

def base.at source · line 173 · raw

@+at:U32 -> @+step:U32 -> @+nm:Nat -> @+o:U32 -> @r:Pair(Array<U32>, U32) -> Array<U32>

A group's column from its last row at (nm + 1 rows): r holds the table and the group's base b, which the last row holds; o is offs[c].

def base.os source · line 182 · raw

@+c:U32 -> @+last:U32 -> @+step:U32 -> @+nm:Nat -> @t:Array<U32> -> @r:Pair(Array<U32>, U32) -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

def base.one source · line 188 · raw

@lt:Bool -> @+c:U32 -> @+last:U32 -> @+step:U32 -> @+nm:Nat -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Column c of a group, when c < K: from its first slot offs[c] plus the group's base, which its last row holds.

def base.cols source · line 197 · raw

@+n:Nat -> @+c:U32 -> @+k:U32 -> @+last:U32 -> @+step:U32 -> @+nm:Nat -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Columns c to c + n of a group.

def base.leaf source · line 205 · raw

@+i:U32 -> @+gc:Nat -> @+w:Nat -> @+m:Nat -> @+kb:Nat -> @+k:U32 -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Lane i: rows group i / 2^gc, columns group i % 2^gc (as Hist.grp.leaf).

def bases source · line 215 · raw

@+d:Nat -> @+i:U32 -> @+gc:Nat -> @+w:Nat -> @+m:Nat -> @+kb:Nat -> @+k:U32 -> @ft:Pair(Array<U32>, Array<U32>) -> @fo:Pair(Array<U32>, Array<U32>) -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Pass 2c's forks, shaped as pass 2a's (Hist.grp), with two handles each to the table and the offsets. Lane i reads the offsets and reads and writes only its group's rows in its columns.

def fwd.go source · line 234 · raw

@+n:Nat -> @+at:U32 -> @+step:U32 -> @+s:U32 -> @r:Pair(Array<U32>, U32) -> Array<U32>

r holds the table and the entry x read at at: it becomes s, and the n entries after it (each step further on) follow, from s + x.

def fwd.at source · line 244 · raw

@+c:U32 -> @+kb:Nat -> @+nb:Nat -> @t:Array<U32> -> @r:Pair(Array<U32>, U32) -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

def fwd.one source · line 249 · raw

@lt:Bool -> @+c:U32 -> @+kb:Nat -> @+nb:Nat -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Column c, when c < K: its entries from its first slot offs[c].

def fwd.cols source · line 258 · raw

@+n:Nat -> @+c:U32 -> @+k:U32 -> @+kb:Nat -> @+nb:Nat -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Columns c to c + n.

def fwds source · line 268 · raw

@+d:Nat -> @+j:U32 -> @+w:Nat -> @+k:U32 -> @+kb:Nat -> @+nb:Nat -> @ft:Pair(Array<U32>, Array<U32>) -> @fo:Pair(Array<U32>, Array<U32>) -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

The forks, shaped as pass 2b's, with two handles each to the table and the offsets. Group j reads only its own offsets and reads and writes only its columns of the table.

def put.at source · line 285 · raw

@+j:U32 -> @+slot:U32 -> @pm:Array<U32> -> @r:Pair(Array<U32>, U32) -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

def put.one source · line 291 · raw

@+j:U32 -> @+slot:U32 -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> 0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two

Key j, whose bucket's next slot is t[slot]: perm[that slot] <- j, and the slot moves on.

def put.fin source · line 295 · raw

@ks:Array<U32> -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> Three

def put.go source · line 301 · raw

@+n:Nat -> @+j:U32 -> @+row:U32 -> @+k1:U32 -> @p:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> @r:Pair(Array<U32>, U32) -> Three

r holds the keys and the key x read at j: key j goes to the next slot of its bucket in the row at row, then the n keys after j. k1 is K - 1.

def puts.leaf source · line 312 · raw

@+i:U32 -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @ks:Array<U32> -> @t:Array<U32> -> @pm:Array<U32> -> Three

Block i (2^e keys from i * 2^e, its row at i * 2^kb).

def puts source · line 321 · raw

@+d:Nat -> @+i:U32 -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @fk:Pair(Array<U32>, Array<U32>) -> @ft:Pair(Array<U32>, Array<U32>) -> @fp:Pair(Array<U32>, Array<U32>) -> Three

Pass 3's forks: d levels down to the blocks under block i, with two handles each to the keys, the table and perm. Block i reads only its keys, reads and writes only its row of the table, and writes perm only at the slots its row hands it: pass 2 gave each (block, bucket) pair its own run of slots, so no two lanes write one slot.

def run.fin source · line 340 · raw

@cs:Array<U32> -> @os:Array<U32> -> @r:Three -> Pair(Array<U32>, Bufs)

def run.puts source · line 345 · raw

@+d:Nat -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @ks:Array<U32> -> @pm:Array<U32> -> @cs:Array<U32> -> @r:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> Pair(Array<U32>, Bufs)

Pass 3.

def run.bases source · line 350 · raw

@lo:Bool -> @+d:Nat -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @ks:Array<U32> -> @pm:Array<U32> -> @cs:Array<U32> -> @t:Array<U32> -> @r:Pair(Array<U32>, U32) -> Pair(Array<U32>, Bufs)

Pass 2c: by fwds when lo (a small table), else by bases.

def run.scan source · line 365 · raw

@lo:Bool -> @+d:Nat -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @ks:Array<U32> -> @pm:Array<U32> -> @r:Three -> Pair(Array<U32>, Bufs)

Pass 2b's scan: offs becomes its exclusive scan.

def run.tots source · line 370 · raw

@+d:Nat -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @ks:Array<U32> -> @pm:Array<U32> -> @cs:Array<U32> -> @os:Array<U32> -> @t:Array<U32> -> Pair(Array<U32>, Bufs)

Pass 2b, after 2a (t): over the 2^h groups' last rows.

def run.sums source · line 381 · raw

@lo:Bool -> @+d:Nat -> @+e:Nat -> @+kb:Nat -> @+k:U32 -> @pm:Array<U32> -> @cs:Array<U32> -> @os:Array<U32> -> @r:0xeef3a8486d410ea6f25709aac90dbf40/histogram.Two -> Pair(Array<U32>, Bufs)

Pass 2a: each group's running column sums (Hist.grp), then 2b. For a small table (lo), no 2a: 2b sums each column over all the rows.

def run source · line 398 · raw

@+b:Nat -> @+k:U32 -> @bufs:Bufs -> @r:Pair(Array<U32>, U32) -> Pair(Array<U32>, Bufs)

Pass 1.

def by_key source · line 507 · raw

@+b:Nat -> @+k:U32 -> @keys:Array<U32> -> @bufs:Bufs -> Pair(Array<U32>, Bufs)

The stable counting sort of keys into k buckets at fork depth b. Returns the keys, unchanged, and the buffers: perm, counts and offs hold the result.

Templates

template gat.at source · line 419 · raw

@-T:Data -> @+j:U32 -> @dst:Array<T> -> @r:Pair(Array<T>, T) -> Sd<T>

template gat.one source · line 424 · raw

@-T:Data -> @+j:U32 -> @+x:U32 -> @p:Sd<T> -> Sd<T>

dst[j] <- src[x].

template gat.fin source · line 429 · raw

@-T:Data -> @pm:Array<U32> -> @p:Sd<T> -> Moved<T>

template gat.go source · line 436 · raw

@-T:Data -> @+n:Nat -> @+j:U32 -> @p:Sd<T> -> @r:Pair(Array<U32>, U32) -> Moved<T>

r holds perm and its entry x at j: dst[j] <- src[x], then the n entries after j.

template moved.join source · line 446 · raw

@-T:Data -> @l:Moved<T> -> @r:Moved<T> -> Moved<T>

template moved.rejoin source · line 451 · raw

@-T:Data -> @p2:Array<U32> -> @s2:Array<T> -> @d2:Array<T> -> @r:Moved<T> -> Moved<T>

template gats source · line 460 · raw

@-T:Data -> @+d:Nat -> @+i:U32 -> @+e:Nat -> @fp:Pair(Array<U32>, Array<U32>) -> @fs:Pair(Array<T>, Array<T>) -> @fd:Pair(Array<T>, Array<T>) -> Moved<T>

The gather's forks: d levels down to blocks of 2^e slots under block i, with two handles each to perm, src and dst. Block i reads only its entries of perm, reads src anywhere (no lane writes src) and writes only its slots of dst.

template gather.pick source · line 478 · raw

@-T:Data -> @ok:Bool -> @+lg:Nat -> @+v:T -> @src:Array<T> -> @dst:Array<T> -> Sd<T>

template gather.dst source · line 485 · raw

@-T:Data -> @ok:Bool -> @+lg:Nat -> @dst:Array<T> -> @r:Pair(Array<T>, T) -> Sd<T>

template gather.run source · line 489 · raw

@-T:Data -> @+b:Nat -> @+lg:Nat -> @pm:Array<U32> -> @p:Sd<T> -> Moved<T>

template gather.at source · line 495 · raw

@-T:Data -> @+b:Nat -> @src:Array<T> -> @r:Pair(Array<U32>, U32) -> @s:Pair(Array<T>, U32) -> Moved<T>

template gather source · line 514 · raw

@-T:Data -> @+b:Nat -> @perm:Array<U32> -> @src:Array<T> -> @dst:Array<T> -> Moved<T>

dst[j] <- src[perm[j]] for every slot j, at fork depth b: with by_key's perm, src in sorted order. src may be any size (indices wrap); dst is reused when it has perm's size, else replaced by a new array. Returns the three arrays.