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


Reply via email to