Writing a fast data page in Ring — five rules and one payload shape
These rules come from building a real application — the savings-circle register that holds 20,000 records in the browser — and every one of them was learned by getting it wrong first. None is folklore: each has a measurement behind it, and together they took the app from sluggish to instant.
1Index your loops — for in copies every item
Ring's for x in aList hands you a copy of each
item. On a list of records, that is a copy of every record, every pass.
Index instead, and read through the list directly:
for x in aDeposits
nSum += x[4]
next
reads in place
nCount = len(aDeposits)
for i = 1 to nCount
nSum += aDeposits[i][4]
next
Note the second half of the rule: len() was hoisted out of
the loop header, because Ring re-evaluates it on every iteration.
2Assignment copies too — mutate through the list
The same value semantics mean o = aMembers[k] gives you a
copy; writing to it updates nothing anyone can see. Mutate through the
list itself:
oM = aMembers[k]
oM[2] = oM[2] + nAmount # the list never changes
writes land
aMembers[k][2] = aMembers[k][2] + nAmount
3Helpers that take big values pay for them
Arguments copy as well. A tidy FieldOf(aRecord, "amount")
helper duplicates the whole record on every call — four fields read means
four copies of the record. In hot loops, read fields inline in one pass
over the record instead of calling out per field.
4Append with +
aList + item is the operator form of append, and it adds a
sublist as one item — so building rows like
aMembers + [cName, 0, 0] does what you expect. It measured
consistently faster than add() in our loops, and reads
better too.
5Functions called from the page take exactly one parameter
ring.call("MyFunc", arg) always passes one argument — so
func MyFunc p is the shape, even when you ignore
p. A zero-parameter function simply errors, and the message
will not tell you why. And one naming trap: do not shadow a builtin —
func Load collides with Ring's own load and
silently fails to define.
+The payload shape: ask your API for rows, not objects
The single biggest lever is not in a loop at all. A JSON object decodes in Ring into a pair-list — one list for the record, one list per field, two items per field. A JSON array row is one list and one item per field. Same data, very different cost:
| 20,000 records, same data | Payload | Load | Heap |
|---|---|---|---|
[{"id":1,"member":"m03",…}] | 1.31 MB | 1358 ms | +89 MB |
[[1,"m03",3,250,"ACTIVE"]] | 0.55 MB | 71 ms | +23 MB |
Fifteen times faster to load, a quarter of the memory. Most of that saved second was never parsing — it was first-touching 89 MB of heap the page immediately threw away. If you control the API, send rows; if you might receive either, the register app shows how to accept both.
All five rules — plus the two the runtime enforces for you — are written at the top of the register app's source, where the next reader meets them before the bug does. For what the finished page feels like at 20,000 records, see Working with data.