Hi all,
SVN r2028/29 introduces "packed ravels" for GNU APL 2.0. This is a large
internal change, so some previously undiscovered issues may surface.
Background
----------
Every APL value is a Shape plus a flat array (ravel) of Cell objects.
Each Cell is a tagged union of 24 bytes. Computing with a ravel of
doubles therefore requires dispatching through the Cell abstraction on
every element, which prevents auto-vectorisation and adds per-element
overhead.
What changed
------------
A new 16-bit "ravel type" field (RavelType) is stored in the VF_Flags
word of each Value. When all cells in a ravel have the same type and
the ravel is longer than a short-value threshold (12 elements), the
ravel is tagged as packed. Scalar functions can then check the ravel
type once and execute a tight typed loop directly over the underlying
data without constructing or dispatching through Cell objects. The
packed ravel is created lazily via try_pack() and inflated back to
Cells on demand via explode().
Supported packed types:
RPT_BOOL, RPT_UNICODE16, RPT_UNICODE32, RPT_INT64,
RPT_FLOAT64, RPT_COMPLEX
Performance measurements (cc = core count, N = 1 000 000 or 10 000 000
elements):
N = 1M (fits roughly in L3 cache), 1 core:
int A-B: 38 ns/elem (no INT64 fast path yet; Cell fetcher used)
flt A-B: 1 ns/elem (FLOAT64 fast path)
int -B: 26 ns/elem
flt -B: 1 ns/elem
N = 10M (DRAM-bound), sequential and parallel:
cc int(A-B) flt(A-B) int(-B) flt(-B) [ns/elem]
1 54 7.6 39 6.6
2 38 5.2 32 7.6
4 42 2.7 27 3.4
6 41 2.7 25 4.6
8 39 2.6 25 4.2
10 36 2.4 32 5.4
The "memory wall" is the observation (Wulf & McKee, 1995) that CPU
clock rates have historically grown much faster than DRAM bandwidth.
For a memory-bound operation like A+B on large arrays, adding more
cores eventually saturates the memory bus rather than the arithmetic
units, so throughput stops scaling. For the FLOAT64 fast path that
wall is hit around 4 cores (7.6 → 2.7 ns, ≈2.8× speedup). Beyond
4 cores the gain is marginal.
INT64 has no dedicated fast path yet; it goes through the Cell fetcher
which has more overhead.
Known limitations / caution
----------------------------
- This is a large structural change. All scalar functions, ⎕CR, and
other code paths accept packed ravels as input, but corner cases
that have not yet been exercised may still surface.
- A future commit will add an INT64 fast path in ScalarFunction.cc
(currently only FLOAT64 has one).
- Please report any regressions to [email protected].
Best Regards,
Jürgen
P.S. The benchmarks were run with Claude Code (https://claude.ai/code);
the SVN commit was done manually.
Savannah project page: https://savannah.gnu.org/projects/apl