The `stunt' branch on GitHub (
https://github.com/toddsundsted/stunt)
contains a list/map performance enhancement based on Josh Benner's
feedback late last year.
If you are using nested lists (or maps), the latest code will
dramatically improve indexed assignment (code like `foo[x][y][x] =
"blah"'). In my specific case (package import in the Stunt core), it
knocked off ~20% of the overall time required to import a large
package (LambdaCore, for example). Given the numbers below, some
applications will see far greater improvement.
Bottom line, the LambdaMOO VM is very conservative, and copies data
more than necessary to ensure values are immutable. Specifically,
when using indexed assignment of a variable holding a collection (list
or map) value, the VM copies data instead of modifying the data in
place, even when the VM is the only thing holding on to the
collection.
Commit c97ac2807caf869f67d5ee1dd234e3f09c6f05f2 fixes this. The
commit is specific to the Stunt server, but could be easily ported to
the stock LambdaMOO server (minus a few lines specific to the map
datatype).
The performance improvement is significant:
Here is a small insert test running on the Stunt server before the fix
(BYTECODE_REDUCE_REF is enabled). The list case is faster than the
map case because list insertion is faster than map insertion, however
when you crank up the number of inserts you will see the performance
delta on lists, as well.
Feb 8 14:22:45: > map tests
Feb 8 14:22:45: > map: initializing map: m = []
Feb 8 14:22:45: > map: running test: m[i] = i
Feb 8 14:22:45: > map: 10000 inserts took 0 sec.
Feb 8 14:22:45: > map: initializing map: m = [1 -> []]
Feb 8 14:22:45: > map: running test: m[1][i] = i
Feb 8 14:23:41: > map: 10000 inserts took 56 sec.
Feb 8 14:23:41: > list tests
Feb 8 14:23:41: > list: initializing list: l = {@l, 0}
Feb 8 14:23:41: > list: running test: l[i] = i
Feb 8 14:23:41: > list: 10000 inserts took 0 sec.
Feb 8 14:23:41: > list: initializing list: l[1] = {@l[1], 0}
Feb 8 14:23:42: > list: running test: l[1][i] = i
Feb 8 14:23:43: > list: 10000 inserts took 1 sec.
Feb 8 14:23:43: > (done)
Enabling BYTECODE_REDUCE_REF speeds up the "m[i] = i" case all by
itself (without this option, the first test in a set is as slow as the
second run). Focus on the nested collection case ("m[1][i] = i").
When faced with nested collections, the LambdaMOO VM performs copies
even when those copies are not strictly necessary (in order to ensure
that values are immutable).
Here's the trace after the fix (for 100 times as many inserts).
Feb 8 14:32:43: > map tests
Feb 8 14:32:43: > map: initializing map: m = []
Feb 8 14:32:43: > map: running test: m[i] = i
Feb 8 14:32:47: > map: 1000000 inserts took 4 sec.
Feb 8 14:32:47: > map: initializing map: m = [1 -> []]
Feb 8 14:32:47: > map: running test: m[1][i] = i
Feb 8 14:32:51: > map: 1000000 inserts took 4 sec.
Feb 8 14:32:51: > list tests
Feb 8 14:32:51: > list: initializing list: l = {@l, 0}
Feb 8 14:32:51: > list: running test: l[i] = i
Feb 8 14:32:51: > list: 1000000 inserts took 0 sec.
Feb 8 14:32:51: > list: initializing list: l[1] = {@l[1], 0}
Feb 8 14:32:51: > list: running test: l[1][i] = i
Feb 8 14:32:51: > list: 1000000 inserts took 0 sec.
Feb 8 14:32:51: > (done)
The latest code also includes a few enhancements to index and range
access.
* Added `^' as an alias for the first element in a sequence (to go
along with `$').
* Added `^' and `$' support to the map datatype (previously these
only worked for lists).
* Added support for range operations on maps.
* Added new syntax to the `for' loop.
The `for' loop now sports the following syntax:
`for v, i in (...)'
When used like this, the first variable is assigned the value from the
collection and the second variable is assigned the index (the position
in the case of lists) or key (in the case of maps).
This change makes iterating over a map more performant. Iteration no
longer requires a lengthly call to get the keys in a map before
actually performing the iteration.
m = [… map … ];
for v, k in (m)
...
endfor
Todd