encore icon indicating copy to clipboard operation
encore copied to clipboard

Encore still scans immutable data structures on message send

Open helanhalvan opened this issue 8 years ago • 6 comments

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.

helanhalvan avatar Dec 27 '16 20:12 helanhalvan

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.

helanhalvan avatar Jan 04 '17 12:01 helanhalvan

A few things:

  1. The problem is not changing the number of workers from 10 to 11 but having a shared data that contains 1 000 000 actors

  2. 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 in array.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.

kikofernandez avatar Jan 04 '17 14:01 kikofernandez

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 Locks 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 type Lock to int

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.

jupvfranco avatar Jan 23 '17 13:01 jupvfranco

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

jupvfranco avatar Jan 23 '17 17:01 jupvfranco

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.

helanhalvan avatar Jan 24 '17 07:01 helanhalvan

Will be fixed when relevant Pony stuff is exploited by compiler.

supercooldave avatar May 04 '17 09:05 supercooldave