-
Notifications
You must be signed in to change notification settings - Fork 1
Description
EEL2's memory management is rather dodgy. When the script accesses a memory address and the page hasn't been allocated, it will just cmalloc() it on the fly! Turns out EEL2 is not magic after all...
I've added printf statements in the ELL2 code to track all calls to calloc() and free().
I've used the following test code:
(
DynGenDef(\memTest, "
@init
buf = 0;
i = 0;
@sample
v = buf[i];
i += 1;
i = min(i, 80000);
(i % 1024) == 0 ? print(i);
out0 = 0;
").send;
)
{ DynGen.ar(1, \memTest) }.play;This is the output:
click me
DynGen
EEL2Adapter
EEL2Adapter: init
malloc() __newBlock_align()
malloc() __newBlock_align()
malloc() __newBlock_align()
free() freeBlocks
malloc() __newBlock_align()
malloc() __newBlock_align()
free() freeBlocks
EEL2Adapter: init done
EEL2Adapter: @init section
EEL2Adapter: @init section done
__NSEEL_RAMAlloc
EnterMutex
calloc() block
memused: 524288
LeaveMutex
0
1024
2048
3072
4096
5120
6144
7168
8192
9216
10240
11264
12288
13312
14336
15360
16384
17408
18432
19456
20480
21504
22528
23552
24576
25600
26624
27648
28672
29696
30720
31744
32768
33792
34816
35840
36864
37888
38912
39936
40960
41984
43008
44032
45056
46080
47104
48128
49152
50176
51200
52224
53248
54272
55296
56320
57344
58368
59392
60416
61440
62464
63488
64512
__NSEEL_RAMAlloc
EnterMutex
calloc() block
memused: 1048576
LeaveMutex
65536
66560
67584
68608
69632
70656
71680
72704
73728
74752
75776
76800
77824
78848
79872
~DynGen
delete VM
~EEL2Adapter
free() freeBlocks
free() freeBlocks
free() __growbuf_resize
NSEEL_VM_FreeRAM
free() block
free() block
free() freeBlocks
As you can see, calloc() is called the first time we access the memory at index 0. This will allocate a "page" of 65536 items (= 512 KB). Once we reach buffer index 65536, the next page is allocated.
Obviously, this is all but realtime safe.
Also, I don't understand why __NSEEL_RAMAlloc needs to call the global mutex. All operations are local to the VM. The only global state is the memory size counter (which could be made atomic). Funnily enough, NSEEL_VM_freeRAM does not use the mutex at all (but NSEEL_VM_freeRAMIfCodeRequested does.)
I see the following potential improvements:
-
do not lock the global mutex when allocating RAM for the VM
-
allow to set an allocator function that is used for allocating memory pages. Two possible strategies:
- use our own global memory pool, possibly protecting it with a spinlock. Actually, since we always (de)allocate the same number of bytes, this could be simple freelist with a number of preallocated nodes.
- use
RTAllocandRTFree.
This would be probably be the nicer option, but we'd have to be careful that these functions are called on the correct thread. AFAICT blocks should only be allocated on the RT thread (via__NSEEL_RAMAllocin one of the code sections) whereas blocks might be freed on either the RT thread (viaNSEEL_VM_freeRAMIfCodeRequested) or on the NRT thread (viaNSEEL_VM_freeRAMwhen the VM is freed). EEL2 would have to tell our allocator in which context a block is freed.
Obviously, all of this can only be done by maintaining our own patched version of EEL2.
In the meantime, one thing we could do is pre-allocate one page of memory after we've created the VM, so at least we don't have to hit the memory allocator for first 65536 items. (This can be done with the NSEEL_VM_preallocram function.) The downside is that every DynGen instance would use 512 KB of memory. That's quite a lot of memory if you have many instances. On the other hand, most of that memory will never be touched and modern machines have plenty of RAM. I just leave this up for discussion.
Of course, none of this is urgent. I just wanted to put everything down so I don't forget it.