A VM performance finding: string arguments are copied on every call

20 views
Skip to first unread message

Mansour Ayouni

unread,
Aug 7, 2026, 10:32:20 AMAug 7
to The Ring Programming Language
Hello Mahmoud,

First, please note that this message was generated with AI and I thought it would be useful to share it with you. If you're not comfortable receiving AI-generated messages like this, please let me know, and I'll stop using this approachin the future.


While building RingScript' WebAssembly runtime of the Ring 1.27 VM, I ran into a
performance behaviour I think is worth sharing. Everything below is
measured on stock `ring.exe` 1.27.0 on Windows — it is not a
WebAssembly issue.

The measurement. Both loops below do the same work — 20,000 calls to
`len()`, which only reads a stored size. Only the size of the argument
differs:

cTiny = "0123456789"
cBig = "a"
while len(cBig) < 1048576 cBig += cBig end

t1 = clock()
nS = 0
for i = 1 to 20000 nS += len(cTiny) next
t2 = clock()
nS2 = 0
for i = 1 to 20000 nS2 += len(cBig) next
t3 = clock()
see "len(10 B) x 20k : " + ((t2-t1)/clockspersecond()*1000) + " ms" + nl
see "len(1 MB) x 20k : " + ((t3-t2)/clockspersecond()*1000) + " ms" + nl

Result: 1 ms vs about 4,900–5,000 ms across four runs. Roughly 20 GB
was copied to answer 20,000 length queries.

The cause is `RING_VM_STACK_PUSHCVAR` in `vm.h`: passing a string
variable to a function copies the whole value onto the VM stack. That is
also what gives Ring its clean value semantics, so it is a trade-off
rather than a bug — but it means any Ring code that scans a large string
is O(length) per touch and O(n²) overall. Parsers, tokenizers, template
engines and text processing all inherit it. A JSON decoder written in
pure Ring reaches about 0.27 MB/s for this reason.

Three possible directions, in rising order of ambition:

1. Borrowed arguments for read-only builtins — `len()`, `ascii()`,
   `left()`, `right()`, `substr()`, `find()` never mutate or retain
   their string argument, so a flag at C-function registration could let
   PUSHCVAR pass a pointer for exactly those. Smallest change, and it
   covers the common cases.
2. Copy-on-write strings — reference-count the buffer, copy on write.
   Helps user-defined functions too, but needs care with the GC.
3. Reuse the existing `RING_OBJTYPE_SUBSTRING` machinery, which already
   expresses "a view into a string".

I am not proposing a patch — the trade-off is yours to weigh.

Best regards,
Mansour

Mahmoud Fayed

unread,
Aug 7, 2026, 10:53:00 AMAug 7
to The Ring Programming Language
Hello Mansour

Thanks for your suggestions :D

Greetings,
Mahmoud

Reply all
Reply to author
Forward
0 new messages