encore
encore copied to clipboard
Encore still scans immutable data structures on message send
This program displays some very bad memory behaviour. Changing the 10
in the a
array to a 11
is expected to not effect memory usage significantly, as the Shared_data
should be shared and the new worker therefore should be a very small active-object. On my machine the memory usage difference is about 150 MB, which is way more then expected. This problem disappears if the data in Shared_data
is changed from type Lock
to int
(in the test I also multiplied the size of Shared_data by 300 to make any difference clear).
class Main {
def main () : void {
var locks = new Shared_data(1000000);
var a = new [Worker](10);
for i in [0..|a|-1] {
a[i]=new Worker(locks,10);
a[i]!do()
};
}
}
class Worker {
locks : Shared_data
size : int
def init(locks:Shared_data, size:int) : void {
this.locks=locks;
this.size=size;
}
def do() : void {
this!do()
}
}
passive class Shared_data {
array : [Lock]
def init(size:int) : void {
this.array = new [Lock](size);
for index in [0..size-1] {
this.array[index]= new Lock()
};
}
}
class Lock {
taken : bool
def init() : void { this.taken=false }
}
EDIT: Disabling the cycle detector, using this code change here: https://github.com/albertnetymk/encore/commit/61b22b544c57613dcb287151c6a949a4a91d08d6 does not seem to effect the memory usage for this example.
Updated to the current version of Encore, disabled the cycle detector and re-ran this test. Updated OP to contain the new compiling code. The issue remains.
A few things:
-
The problem is not changing the number of workers from 10 to 11 but having a shared data that contains 1 000 000 actors
-
Each worker has a reference to a passive object with an array of one million active objects. Your workers iterate in an infinite loop
this!do()
. My guess is that when you send a message, this incurs in tracing the shared data which tries to trace up to the actor level. When it reaches the array of actors, it needs to trace each actor (array_trace
function inarray.c
). Since it takes 150 MB more, I believe that this is due to having a data structure to do the tracing book keeping on a per actor level. Now, because you said that there's 150 MB extra, we can calculate the size of this data structure by 150 MB / 1 000 000 = 150 bytes of overhead per actor. If this assumption is correct, every time you add a new worker you should expect to consume an extra 150 MB for this particular case.
For this type of tests, it is good that the program execution ends at some point, so I removed the recursive calls to do
, and did some runs with time -l
.
Here's what I got:
- number of workers = 10 10 seconds and 2.14GB
- number of workers = 11 10.6 seconds and 2.25GB
The difference in memory usage is not related with the allocation of the extra worker, as each actor requires around 250 bytes of space.
I believe this is related with message tracing (I am not 100% sure though). 1 more worker means 1 more message init(locks)
sent, which means tracing of locks
upon message sending and message receiving.
If Lock
s are passive objects, rather than actors, you will also see some difference in memory usage for different number of workers.
(Note that when Locks are actors the program uses much more memory, which is expected.)
This problem disappears if the data in
Shared_data
is changed from typeLock
toint
This is because tracing an array of integers is way cheaper than tracing an array of pointers.
This problem also disappears if Shared_data
becomes an actor---the locks data structure will never be traced when passed around.
@kikofernandez and I have discussed this with @albertnetymk and it should be related with the size of a data structure used for tracing. Probably, Albert can give more details on that.
One more thing: I've shown this issue to @sylvanc and he says that it may be related with https://github.com/ponylang/ponyc/issues/1118
1 more worker means 1 more message init(locks) sent, which means tracing of locks upon message sending and message receiving.
If this is really the problem, I would expect to see a memory usage spike first, and then see the memory usage lowering, as the tracing is a thing that's done once, and then stops using memory right? That tracing causes a memory usage spike when the program starts is expected behavior, (or at least acceptable behavior), however it's the static high memory usage that troubles me.
Will be fixed when relevant Pony stuff is exploited by compiler.