carray-jit 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -0
- data/README.md +2 -2
- data/docs/02_KernelShapes.md +66 -5
- data/docs/04_Compiling.md +11 -1
- data/docs/06_Cheatsheet.md +19 -7
- data/examples/applications/point_cloud.rb +23 -11
- data/lib/carray/jit/analyzer.rb +145 -26
- data/lib/carray/jit/c_function.rb +37 -14
- data/lib/carray/jit/c_generator.rb +12 -0
- data/lib/carray/jit/compiler.rb +19 -1
- data/lib/carray/jit/kernel.rb +2 -1
- data/lib/carray/jit/type_assignment.rb +15 -13
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +397 -20
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 988f9266a5aab773463b6e24ff6a9316993d2a820d17876f34bb048809066437
|
|
4
|
+
data.tar.gz: 4cf44dafad927c4d4b20e02aa6f057bebc6681b360fdfba396cc9c620f637fdb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 693790997ddab3484d377113866e6059bf29869ecc1921989ba1aaba76a33ea88f33b97ca7876832707d585e27061df37582c8710303e0a23a5cf56a4dbaaf30
|
|
7
|
+
data.tar.gz: 620704ee02ec5c638c185f8c416d98a8d45955634f2953cdef5f08613c6112faaba5b4c87a4b2bf7b928f4223370e75ba096c7fa7368939a3f4a1e0769c15387
|
data/CHANGELOG.md
CHANGED
|
@@ -37,6 +37,92 @@ version you have and a newer one.
|
|
|
37
37
|
a release needs is said in the gemspec, and an entry says so only
|
|
38
38
|
when the answer changes. -->
|
|
39
39
|
|
|
40
|
+
## 0.1.2
|
|
41
|
+
|
|
42
|
+
- New: `CArray.jit_contract` takes the result's axes as symbols, which says
|
|
43
|
+
which indices are free: `CArray.jit_contract(:p) { |k| x[p,k] * y[p,k] }` is
|
|
44
|
+
one number per point, and `CArray.jit_contract(:a) { q[a,a] }` is the
|
|
45
|
+
diagonal rather than the trace. A named index stays free however often it
|
|
46
|
+
appears, which is what an index that numbers things -- a point, a sample, a
|
|
47
|
+
batch -- does. What a repetition means is unchanged, so the whole rule is
|
|
48
|
+
that an index which repeats is summed and one that is named is free. Naming
|
|
49
|
+
the axes also states their order. With no arguments nothing changes.
|
|
50
|
+
|
|
51
|
+
- New: `CArray::JIT.contract_terms` runs the contraction a structure describes
|
|
52
|
+
rather than one a block writes --
|
|
53
|
+
`CArray::JIT.contract_terms([[a, [:i, :k]], [b, [:k, :j]]], free: [:i, :j])`
|
|
54
|
+
is a matrix product -- and `CArray::JIT.contraction_of` reads a block and
|
|
55
|
+
returns the terms it is a product of, or nil when it is not one. They are
|
|
56
|
+
`jit_contract` with the block taken out of the middle, for a caller that
|
|
57
|
+
rearranges a contraction before running it: the terms are compiled by the
|
|
58
|
+
same analyzer under the same rules, so `free:` is required and an index that
|
|
59
|
+
is not named must appear at more than one position.
|
|
60
|
+
|
|
61
|
+
- New: `CArray::JIT.cache_root = "path"` puts an application's compiled
|
|
62
|
+
kernels somewhere of its own, rather than in the cache shared under the home
|
|
63
|
+
directory. Say it before the first kernel is compiled; the path is expanded
|
|
64
|
+
where it is given. `CARRAY_JIT_CACHE` and `CARRAY_JIT_NO_CACHE` still come
|
|
65
|
+
first, and `nil` restores the default.
|
|
66
|
+
|
|
67
|
+
- Change: a contraction's sum is split into partial sums, as `jit_for`'s
|
|
68
|
+
reduction and CArray's own reduce kernels are, which makes `jit_contract` as
|
|
69
|
+
fast as the same loop written with `jit_for` rather than three times slower.
|
|
70
|
+
A floating-point contraction therefore answers what the split accumulation
|
|
71
|
+
answers -- usually the more accurate number, never the one a serial Ruby
|
|
72
|
+
loop gives. `CArray::JIT.reassociate = false`, or `CARRAY_JIT_REASSOCIATE=0`
|
|
73
|
+
for a whole process, asks for the serial order; `jit_contract` takes no
|
|
74
|
+
per-call licence. Integer contractions are unaffected.
|
|
75
|
+
|
|
76
|
+
- Change: `CArray.jit_contract` sums an index that repeats however often it
|
|
77
|
+
repeats, rather than refusing more than two positions. `q[i,i,i]` is the sum
|
|
78
|
+
along a cube's long diagonal, and `a[i,k] * b[k,k]` sums `k` at three
|
|
79
|
+
positions across two arrays. Nothing that compiled before compiles
|
|
80
|
+
differently: what changes is that these are accepted instead of raising
|
|
81
|
+
`CArray::JIT::Unsupported`.
|
|
82
|
+
|
|
83
|
+
- Fix: a contraction with nothing to assign into is collected into the type its
|
|
84
|
+
summand computes in. `CArray.jit_contract { |i, j, k| a[i,k] * b[k,j] }` over
|
|
85
|
+
float32 arrays came back int64 with every value truncated; over uint64 it came
|
|
86
|
+
back int64 and wrapped; over cmplx64 it raised. The assigned form -- the same
|
|
87
|
+
contraction written `c[i,j] = ...` -- was right throughout, and `jit_for`,
|
|
88
|
+
`jit_each` and `jit_stencil` were never affected.
|
|
89
|
+
|
|
90
|
+
- Fix: a contraction that writes an array it also reads is refused when the
|
|
91
|
+
block gave that array two names -- `y = x`, or a view of something being
|
|
92
|
+
read -- as it always was when one name was used for both. It compiled and
|
|
93
|
+
returned an answer that depended on the order the cells were reached in.
|
|
94
|
+
`CArray::JIT.contract_terms` refuses `into:` for the same reason. Write it
|
|
95
|
+
with `jit_for`, which is what a recurrence is for.
|
|
96
|
+
|
|
97
|
+
- Fix: `CArray.jit_stencil(source, into: source)` is refused rather than
|
|
98
|
+
computing a pass whose cells feed the ones after them. A window that reaches
|
|
99
|
+
nowhere -- one that reads only the cell it is on -- still writes in place, as
|
|
100
|
+
it always did.
|
|
101
|
+
|
|
102
|
+
- Fix: assigning to a loop index inside a kernel is refused. `jit_for(3) { |i|
|
|
103
|
+
i = 2; out[i] = ... }` assigned to the counter, so the loop walked somewhere
|
|
104
|
+
else -- outside the array, for a value outside its extent -- while Ruby reads
|
|
105
|
+
the same line as rebinding the parameter and runs the loop unchanged. Use a
|
|
106
|
+
local of another name; nothing that has an index only on the right changes.
|
|
107
|
+
|
|
108
|
+
- Fix: an index named after something the generated C already uses is refused
|
|
109
|
+
where it is written rather than by the compiler. `jit_contract { |int, j, k|
|
|
110
|
+
... }` reached clang as a declaration of `int`, and an index named
|
|
111
|
+
`contraction` shared its identifier with the accumulator the compiler writes,
|
|
112
|
+
which made the sum come out zero with nothing said.
|
|
113
|
+
|
|
114
|
+
- Fix: a C function may take a `uint64_t *` and return a `uint64_t`.
|
|
115
|
+
`CArray.jit_function("void (*)(uint64_t *, int64_t)")` reached its cells as
|
|
116
|
+
an opaque slot rather than an array, and a `uint64_t` return type was
|
|
117
|
+
refused as "no value a compiled body can produce" -- from a table written
|
|
118
|
+
before uint64 was a type a kernel computes in. A `uint64_t` parameter taken
|
|
119
|
+
by value is still refused, and now says why: a value reaches a body as a
|
|
120
|
+
double, an int64 or a complex, and a uint64 fits none of them whole.
|
|
121
|
+
|
|
122
|
+
- Fix: `CArray.jit_map` collects a cmplx64 value into a cmplx64 array rather
|
|
123
|
+
than refusing to allocate one. Nothing else changes type: a block whose value
|
|
124
|
+
is cmplx128 still gives cmplx128.
|
|
125
|
+
|
|
40
126
|
## 0.1.1
|
|
41
127
|
|
|
42
128
|
- Fix: a zero divisor in a `CArray.fuse` expression no longer turns
|
data/README.md
CHANGED
|
@@ -8,13 +8,13 @@ The block is read with Prism, translated to C if it falls inside that subset, co
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
0.1.
|
|
11
|
+
0.1.2 is the current release, and it still moves: behaviour can change between releases — see [CHANGELOG.md](CHANGELOG.md). A companion gem to CArray, it follows CArray's surface, which is not settled until CArray 3.1.
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
15
|
- **A JIT compiler for C-level loops.** A block becomes one C function over CArray's own memory, built by the system C compiler and called through Fiddle.
|
|
16
16
|
- **Ordinary Ruby, and enough of it.** The source is parsed with Prism -- no DSL, no `eval` -- and every operation means what Ruby means by it, apart from the order a reduction takes its terms in. The subset is enough to state a numerical algorithm; what falls outside it is refused by name and line, not run as a Ruby loop.
|
|
17
|
-
- **A method for each shape.** `jit_for` for recurrences and loops written out, `jit_stencil` for windows at any rank, `CArray.jit_contract` for contraction over a repeated index, `jit_each` and `jit_map` for a pass that reaches no neighbour.
|
|
17
|
+
- **A method for each shape.** `jit_for` for recurrences and loops written out, `jit_stencil` for windows at any rank, `CArray.jit_contract` for contraction over a repeated index, or over the indices left when the result's axes are named, `jit_each` and `jit_map` for a pass that reaches no neighbour.
|
|
18
18
|
- **View- and mask-aware.** Columns, transposes and slices of slices are written in place without a copy, and masks propagate as CArray propagates them.
|
|
19
19
|
- **Pure C functions, in and out.** `jit_extern` binds one from a library and a kernel calls it by address; `jit_function` compiles one from a block and hands back a C function pointer.
|
|
20
20
|
- **The backend for `CArray.fuse`.** An array expression compiles instead of being walked a node at a time, without being asked and without changing the answer.
|
data/docs/02_KernelShapes.md
CHANGED
|
@@ -355,7 +355,7 @@ So this is not a faster `sum`. It is a way to write the reduction that has no `s
|
|
|
355
355
|
|
|
356
356
|
## Contraction
|
|
357
357
|
|
|
358
|
-
`CArray.jit_contract` contracts over a repeated index: **an index that
|
|
358
|
+
`CArray.jit_contract` contracts over a repeated index: **an index that repeats in the term is summed**. The repetition is the notation -- it is what stands in for the sigma. How often it repeats does not enter into it: `q[i,i,i]` is one index read at three positions, and the sum runs along the cube's long diagonal.
|
|
359
359
|
|
|
360
360
|
```ruby
|
|
361
361
|
c = CArray.jit_contract { |i, j, k| a[i,k] * b[k,j] } # a matrix product
|
|
@@ -384,14 +384,75 @@ Neither form decides what is summed. So a sum along an axis is not a contraction
|
|
|
384
384
|
```ruby
|
|
385
385
|
CArray.jit_contract { |i, k| total[i] = a[i,k] }
|
|
386
386
|
#=> `k` appears once, so it is free and must be on the left. A contraction
|
|
387
|
-
# sums the indices that
|
|
388
|
-
#
|
|
387
|
+
# sums the indices that repeat; to sum one that does not, write the loop
|
|
388
|
+
# with jit_for, or use sum(axis:)
|
|
389
389
|
```
|
|
390
390
|
|
|
391
391
|
There is nothing in `a[i,k]` standing in for a sigma, and summing anyway would be the `=` quietly meaning something it does not say. `sum(axis: 1)` is that operation, and it is faster than anything written here.
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
### Naming the result's axes
|
|
394
|
+
|
|
395
|
+
That a repeated index is summed is a statement about *dimensions*, which is the world the notation comes from: two dimensions met is an inner product, and there is no other reading. An index that numbers things -- a point, a sample, a batch -- is not a dimension. `x[p,k] * y[p,k]` repeating `p` says "the same point", not "sum over points", and the convention cannot tell the two apart. Naming the result's axes says which is meant:
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
n = CArray.jit_contract(:p) { |k| x[p,k] * y[p,k] } # one number per point
|
|
399
|
+
d = CArray.jit_contract(:a) { q[a,a] } # the diagonal, not the trace
|
|
400
|
+
r = CArray.jit_contract(:b, :i, :j) { |k| u[b,i,k] * v[b,k,j] } # a batch of products
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The arguments are the result's axes, in that order. What they say is which indices are free; they do not say what a repetition means, and a repetition still means a sum. So the whole of it is the convention's rule with a clause added:
|
|
404
|
+
|
|
405
|
+
**An index that repeats is summed; one that appears once is free; and a named one is free however often it appears.**
|
|
406
|
+
|
|
407
|
+
The third clause is what puts the per-point quantity and the diagonal inside the notation instead of outside it -- `q[a,a]` is the trace under the convention and the diagonal when the axis is named.
|
|
408
|
+
|
|
409
|
+
A free index then needs somewhere to go, and there are three places: the argument list, the left-hand side, or -- with neither -- the result's axes, which are the free indices in the order the block's parameters named them. So a parameter at a single position is refused once the axes are named. It is free, by the second clause, and the axes are already stated:
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
`k` appears once, so it is free rather than summed. A contraction sums the
|
|
413
|
+
indices that repeat; name it as an axis of the result
|
|
414
|
+
(`CArray.jit_contract(:i, :k)`) to keep it, or use sum(axis:) to sum along
|
|
415
|
+
the axis
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
So the list is all of the result's axes rather than some of them: name one and you have named them all. A partial one could be given a meaning -- the axes left out would take their order from the parameter list, as they do when nothing is named -- but it would only ever produce the orders that put the named axes first, so an order like `[i, b, j]` with `b` named could not be asked for at all. The result's order would be stated in two places, and neither could state all of it.
|
|
419
|
+
|
|
420
|
+
With every index named there is nothing left to sum, and the block takes no parameters at all.
|
|
421
|
+
|
|
422
|
+
This is the split `einsum` makes with `->`: `'ii'` is the trace and `'ii->i'` the diagonal, `'pk,pk'` is one number and `'pk,pk->p'` one per point. The argument list is that arrow's right-hand side.
|
|
423
|
+
|
|
424
|
+
Naming is allowed where the convention would have reached the same answer, which is how the result's axes are put in another order:
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
CArray.jit_contract(:j, :i) { |k| a[i,k] * b[k,j] } # the product, transposed
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
An index cannot be both, and saying so twice is refused. Everything else is as it is under the convention: the extents come from the axes, an index whose axes disagree is refused, and assigning into an array of your own says where the result goes.
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
An array that is both written and read is a recurrence rather than a contraction, and is refused with a pointer at `jit_for` too -- under any name it was given. `y = x` is one array with two names, and so is a view of something being read; both are followed to the memory they share rather than compared by name.
|
|
435
|
+
|
|
436
|
+
### The contraction without the block
|
|
437
|
+
|
|
438
|
+
A contraction that was *decided* rather than written has no block to read: a caller that rearranges one -- contracting two terms at a time, in an order it chose -- holds a structure, and a structure has no source. Two methods are the same contraction with the block taken out of the middle:
|
|
439
|
+
|
|
440
|
+
```ruby
|
|
441
|
+
CArray::JIT.contraction_of(proc { |i, j, k| a[i,k] * b[k,j] })
|
|
442
|
+
#=> { :terms => [[a, [:i, :k]], [b, [:k, :j]]], :free => [:i, :j], :summed => [:k] }
|
|
443
|
+
|
|
444
|
+
CArray::JIT.contract_terms([[a, [:i, :k]], [b, [:k, :j]]], free: [:i, :j])
|
|
445
|
+
#=> the same matrix product
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
`contract_terms` writes the block that was not written and compiles that, so the rules, the errors and the kernels are the ones above -- `free:` is the result's axes in order and is required, since a structure has no parameter list to say it in; an index that is not named is summed and must appear at more than one position; `into:` writes into an array of yours.
|
|
449
|
+
|
|
450
|
+
`contraction_of` returns nil when there is nothing to take apart: a summand that is more than a product of cells (`Math.exp(a[i,k]) * b[k,j]`, a division, a captured number, an index with an offset) or one that assigns into an array of its own. Nil is not a refusal -- `jit_contract` compiles all of those -- it says the block is not a product with pieces to rearrange.
|
|
451
|
+
|
|
452
|
+
Taken together they are what a contraction-order optimizer needs, and nothing else: a three-term product taken apart, contracted two at a time and put back agrees with the single nest to within the tolerance rearranging the additions earns, and on an 8x8x8x8 chain it is already faster.
|
|
394
453
|
|
|
395
454
|
There is no BLAS for an arbitrary contraction, which is rather the point: this compiles to a plain nest of loops and is slower than a tuned GEMM, but it is one line and it exists.
|
|
396
455
|
|
|
397
|
-
Its sum is
|
|
456
|
+
Its sum is split into partial ones, as `jit_for`'s reduction is and as CArray's own reduce kernels are. A contraction is a sum with no loop written anywhere for its order to agree with -- the notation says which indices are summed and nothing about in what order -- so there is nothing being overridden, which is a weaker claim than the one `jit_for` makes over a loop somebody wrote. It is also the whole of the difference between a contraction and that loop: a 400 x 400 x 400 product took 47 ms serial against `jit_for`'s 16, and takes 16 split (`benchmark/contraction.rb`).
|
|
457
|
+
|
|
458
|
+
`CArray::JIT.reassociate = false`, and `CARRAY_JIT_REASSOCIATE=0` for a whole process, ask for the serial order here as they do everywhere -- which is the order a Ruby loop takes, and what to use to compare one against the other. Unlike `jit_for`, `jit_contract` has no per-call licence: a contraction names no loop, so there is no loop at the call site to license.
|
data/docs/04_Compiling.md
CHANGED
|
@@ -89,6 +89,16 @@ process C, existing file dlopen 0.2 ms
|
|
|
89
89
|
|
|
90
90
|
Set `CARRAY_JIT_NO_CACHE=1` (or `CARRAY_JIT_CACHE=none`) to put the cache in a temporary directory that is removed at exit, at that cost per kernel per run.
|
|
91
91
|
|
|
92
|
+
An application that would rather not share the cache under the home directory can name one of its own, before the first kernel is compiled:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
CArray::JIT.cache_root = File.expand_path("../.jit-cache", __dir__)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The path is expanded where it is given, so a relative one does not move when the program does. What is already compiled and loaded keeps working, and what is already on disk stays where it is; this says where the next kernel is looked for and written. A directory inside a project wants to be ignored by the version control it sits in.
|
|
99
|
+
|
|
100
|
+
`CARRAY_JIT_CACHE` and `CARRAY_JIT_NO_CACHE` still come first, so whoever runs the program has the last word on where a cache may be written and whether there is one at all. `CArray::JIT.cache_root = nil` restores the default.
|
|
101
|
+
|
|
92
102
|
The cache is **bounded**: past `CARRAY_JIT_CACHE_LIMIT` kernels per environment (512 by default, so about 9 MB) the least recently used are evicted, source and object together. Reuse updates an entry's timestamp, so what a program actually runs stays. This is the one place carray-jit deliberately parts with RubyInline, whose `~/.ruby_inline` has no eviction at all and grows for the life of the account.
|
|
93
103
|
|
|
94
104
|
Removing a cached object never breaks a kernel already in use: unlinking a loaded shared object leaves its mapping intact.
|
|
@@ -203,7 +213,7 @@ carray_jit_contiguous (char **pointers, int64_t *strides, int64_t *bounds, ...)
|
|
|
203
213
|
| Variable | Effect |
|
|
204
214
|
| --- | --- |
|
|
205
215
|
| `CARRAY_JIT_DUMP` | Print generated C to stderr before compiling |
|
|
206
|
-
| `CARRAY_JIT_CACHE` | Cache directory (default `~/.cache/carray-jit`), or `none` |
|
|
216
|
+
| `CARRAY_JIT_CACHE` | Cache directory (default `~/.cache/carray-jit`, or what `CArray::JIT.cache_root=` named), or `none` |
|
|
207
217
|
| `CARRAY_JIT_NO_CACHE` | Keep the cache in a temporary directory, removed on exit |
|
|
208
218
|
| `CARRAY_JIT_CACHE_LIMIT` | Kernels retained on disk, per environment (default 512) |
|
|
209
219
|
| `CARRAY_JIT_CACHE_MAX_AGE_DAYS` | Days an unused environment's directory is kept (default 30) |
|
data/docs/06_Cheatsheet.md
CHANGED
|
@@ -13,7 +13,8 @@ installed. Every example here runs as written.
|
|
|
13
13
|
| The same, and you want the result back | `CArray.jit_map` |
|
|
14
14
|
| A cell reads its neighbours, or the one computed before it | `CArray.jit_for` |
|
|
15
15
|
| A cell reads a window, and the edge needs a rule | `CArray.jit_stencil` |
|
|
16
|
-
| An index
|
|
16
|
+
| An index repeats and is summed | `CArray.jit_contract` |
|
|
17
|
+
| An index repeats and is *not* summed -- a point number, a batch | `CArray.jit_contract(:p)`, naming the result's axes |
|
|
17
18
|
| Call a C function someone else compiled | `CArray.jit_extern` |
|
|
18
19
|
| Compile a C function of your own | `CArray.jit_function` |
|
|
19
20
|
|
|
@@ -91,12 +92,22 @@ is what the cell gets.
|
|
|
91
92
|
```ruby
|
|
92
93
|
c = CArray.jit_contract { |i, j, k| a[i,k] * b[k,j] } # a matrix product
|
|
93
94
|
CArray.jit_contract { |i, j, k| c[i,j] = a[i,k] * b[k,j] }
|
|
95
|
+
n = CArray.jit_contract(:p) { |k| x[p,k] * y[p,k] } # the axes named: one per point
|
|
96
|
+
d = CArray.jit_contract(:a) { q[a,a] } # the diagonal, not the trace
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
**An index that
|
|
97
|
-
becomes an axis of the result, in the order the block named
|
|
98
|
-
`{ |j, i, k| ... }` is the transpose.
|
|
99
|
-
|
|
99
|
+
**An index that repeats is summed**, however often it repeats. One that appears
|
|
100
|
+
once is free and becomes an axis of the result, in the order the block named
|
|
101
|
+
them -- so `{ |j, i, k| ... }` is the transpose. **An index named in the
|
|
102
|
+
arguments is free however often it appears**, which is the third clause and
|
|
103
|
+
what puts the diagonal and the per-point quantity here rather than in a loop.
|
|
104
|
+
|
|
105
|
+
Naming one axis names them all: the argument list is the result's axes and
|
|
106
|
+
their order, so a parameter left at a single position is refused there as it is
|
|
107
|
+
under the convention. No extent is given: each index's extent comes from the
|
|
108
|
+
axes it addresses. The sum is split into partial sums, as `jit_for`'s reduction
|
|
109
|
+
is -- `CArray::JIT.reassociate = false` is the serial order, and a contraction
|
|
110
|
+
has no per-call licence of its own.
|
|
100
111
|
|
|
101
112
|
Assigning into an array of yours says where to put it and in what order its
|
|
102
113
|
axes lie. It does **not** decide what is summed -- so `total[i] = a[i,k]` is
|
|
@@ -155,7 +166,7 @@ that was not asked.
|
|
|
155
166
|
| `jit_stencil` with no array given | the arrays are arguments, not closures |
|
|
156
167
|
| `jit_stencil` with both `type:` and `into:` | `into:` already decides the type |
|
|
157
168
|
| a contraction summing an index that appears once | not the convention; `sum(axis:)` |
|
|
158
|
-
|
|
|
169
|
+
| a parameter at one position once the axes are named | it is free, and the axes are already stated |
|
|
159
170
|
| an index whose axes disagree in extent | the shape check a contraction exists to do |
|
|
160
171
|
| an array both written and read in a contraction | a recurrence -- write it with `jit_for` |
|
|
161
172
|
| a block naming a construct outside the subset | refused by name and line |
|
|
@@ -163,10 +174,11 @@ that was not asked.
|
|
|
163
174
|
## Knobs
|
|
164
175
|
|
|
165
176
|
```ruby
|
|
166
|
-
CArray::JIT.reassociate #=> true,
|
|
177
|
+
CArray::JIT.reassociate #=> true, for jit_for reductions and every contraction
|
|
167
178
|
CArray::JIT.reassociate = false # serial accumulation everywhere
|
|
168
179
|
|
|
169
180
|
CArray::JIT.cache_directory #=> ~/.cache/carray-jit/<version>
|
|
181
|
+
CArray::JIT.cache_root = "tmp/jit" # this application keeps its own
|
|
170
182
|
CArray::JIT.cache_entry_count
|
|
171
183
|
CArray::JIT.cache_byte_size
|
|
172
184
|
CArray::JIT.clear_cache
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rotating a point cloud, then projecting it onto a basis
|
|
2
2
|
#
|
|
3
|
-
# Both are sums over an index that
|
|
3
|
+
# Both are sums over an index that repeats, which is what
|
|
4
4
|
# `CArray.jit_contract` is: the repeated index is summed, so the notation is the
|
|
5
5
|
# formula.
|
|
6
6
|
#
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
# cov[a,b] = sum_p c[p,a] c[p,b] the covariance of the cloud
|
|
9
9
|
# coeff[p,m] = sum_k x[p,k] basis[m,k] project onto a basis
|
|
10
10
|
# recon[p,k] = sum_m coeff[p,m] basis[m,k] and build the points back
|
|
11
|
+
# norm[p] = sum_k x[p,k] x[p,k] how far each point is out
|
|
12
|
+
#
|
|
13
|
+
# The last one is where the convention needs help: p appears twice there as
|
|
14
|
+
# well, but it numbers the points rather than naming a dimension, so it is
|
|
15
|
+
# named as the result's axis and only k is summed.
|
|
11
16
|
#
|
|
12
17
|
# Written as loops these are three lines each and easy to get subtly wrong: an
|
|
13
18
|
# index in the wrong place transposes the answer rather than failing. Here the
|
|
@@ -69,18 +74,21 @@ puts format(" dropping the third direction costs %.2e per point", residual)
|
|
|
69
74
|
puts format(" which is the variance that was in it: %.2e",
|
|
70
75
|
rotated[nil, 2].stddev ** 2)
|
|
71
76
|
|
|
72
|
-
# The distance of every point from the origin
|
|
73
|
-
#
|
|
74
|
-
# twice
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
(0...3).each { |k| total = total + rotated[p, k] * rotated[p, k] }
|
|
80
|
-
squared[p] = total
|
|
81
|
-
}
|
|
77
|
+
# The distance of every point from the origin, and the place the convention
|
|
78
|
+
# alone would say the wrong thing: in `x[p,k] * x[p,k]` the index p appears
|
|
79
|
+
# twice, so it would be summed as well and the answer would be one number for
|
|
80
|
+
# the whole cloud. p is not a dimension, though -- repeating it says "the
|
|
81
|
+
# same point" -- so it is named as the result's axis, and then k is the only
|
|
82
|
+
# index left to sum.
|
|
83
|
+
squared = CArray.jit_contract(:p) { |k| rotated[p,k] * rotated[p,k] }
|
|
82
84
|
puts format(" furthest point %.4f away", Math.sqrt(squared.max))
|
|
83
85
|
|
|
86
|
+
# Name nothing and the same term is the other reading, which is the number
|
|
87
|
+
# the per-point one adds up to.
|
|
88
|
+
whole = CArray.jit_contract { |p, k| rotated[p,k] * rotated[p,k] }
|
|
89
|
+
puts format(" the cloud's total %.1f, and per point summed again %.1f",
|
|
90
|
+
whole[0], squared.sum)
|
|
91
|
+
|
|
84
92
|
# The same rotation written as a Ruby loop.
|
|
85
93
|
reference = CArray.double(count, 3)
|
|
86
94
|
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -99,6 +107,10 @@ compiled = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
|
99
107
|
|
|
100
108
|
puts format(" rotation: %.1f ms compiled, %.0f ms as a Ruby loop (%.0fx)",
|
|
101
109
|
compiled * 1e3, interpreted * 1e3, interpreted / compiled)
|
|
110
|
+
# The two agree to the last bit here, which is not the general case: a kernel
|
|
111
|
+
# splits its sum into partial ones, and only the serial loop's order gives the
|
|
112
|
+
# serial loop's answer. The sum is over j, whose extent is three, so the eight
|
|
113
|
+
# partial sums never fill a round and what runs is the serial tail.
|
|
102
114
|
puts " identical: #{rotated.to_a == reference.to_a}"
|
|
103
115
|
|
|
104
116
|
# An index whose axes disagree is the mistake this notation exists to catch.
|