[Pharo-project] serializing object graphs / ordering graph anchors
Before I start a utility myself I want to ask if there is such a tool already. I'm using sixx for object graph serialization. It works quite good but it has some severe limits. Well, the limits are not really sixx dependent but a problem of usual serializers. My problem is the size of the stack such a tool generates. Especially in gemstone this appears to be a problem because the stack is limited per gem. If I look at the serializers I know the mechanics are simple: 1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1. If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000. One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course. Does anybody know if such a tool exists? Norbert
On Thu, Jun 2, 2011 at 12:38 PM, Norbert Hartl <norbert@hartl.name> wrote:
Before I start a utility myself I want to ask if there is such a tool already.
I'm using sixx for object graph serialization. It works quite good but it has some severe limits.
I am interesting in listening which ones apart from performance/speed.
Well, the limits are not really sixx dependent but a problem of usual serializers. My problem is the size of the stack such a tool generates.
The stack is a problem??? By stack you mean stack of MethodContexts ? why do you say a "usual" problem of serializers? do you mean because of the fact the the graph is usually traversed with a recursion? Especially in gemstone this appears to be a problem because the stack is
limited per gem.
In which sense the stack is limited?
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
-- Mariano http://marianopeck.wordpress.com
Am 02.06.2011 um 18:35 schrieb Mariano Martinez Peck:
On Thu, Jun 2, 2011 at 12:38 PM, Norbert Hartl <norbert@hartl.name> wrote: Before I start a utility myself I want to ask if there is such a tool already.
I'm using sixx for object graph serialization. It works quite good but it has some severe limits.
I am interesting in listening which ones apart from performance/speed.
The limit I described below.
Well, the limits are not really sixx dependent but a problem of usual serializers. My problem is the size of the stack such a tool generates.
The stack is a problem??? By stack you mean stack of MethodContexts ?
yes
why do you say a "usual" problem of serializers? do you mean because of the fact the the graph is usually traversed with a recursion?
yes
Especially in gemstone this appears to be a problem because the stack is limited per gem.
In which sense the stack is limited?
It is limited in size. It is a configured maximum where a stack size of 1000 is default AFAIK.
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that? Norbert
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
-- Mariano http://marianopeck.wordpress.com
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that?
In 4. you said "while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.". So... Imagine object X with two instVars Y and Z. Imagine the method of the traverse is called #analye:. So you do #analize: X. Then, in such method you check whether X has regular pointers to regular objects, and if true, you analize them. So in this case you would send #analyze: Y and #analize: Z, generating the recursion. Right ? Ok, instead of sending #analyze: Y and #analize: Z what we do is just to do a push on a stack: #push: Y and #push: Z. And then we are done with X, so we pop from the stack and we continue with the next object (at some point in the future we will pop Y and Z) When there are no more objects in the stack it means we are done. For performance (I think) we did FLSimpleStack. You can check also FLSimpleStackTest. Both are in Fuel repo. Would something like that help for the stack in GemStone? Cheers
Norbert
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
Am 03.06.2011 um 10:20 schrieb Mariano Martinez Peck:
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that?
In 4. you said "while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.". So... Imagine object X with two instVars Y and Z. Imagine the method of the traverse is called #analye:. So you do #analize: X. Then, in such method you check whether X has regular pointers to regular objects, and if true, you analize them. So in this case you would send #analyze: Y and #analize: Z, generating the recursion. Right ?
yes, the time between analyse: Y and analyse: Z is dependent on the graph that is attached to Y. Y can have objects that have further objects etc. So leaving X aside, Y can be the first object to serialize and Z the last.
Ok, instead of sending #analyze: Y and #analize: Z what we do is just to do a push on a stack: #push: Y and #push: Z. And then we are done with X, so we pop from the stack and we continue with the next object (at some point in the future we will pop Y and Z) When there are no more objects in the stack it means we are done.
How do you write X? I can see that you turn a recursive trace into depth-by-depth trace but the difficult part is to write X. In X you need a reference representation of any sort. How do you derive that from the stack?
For performance (I think) we did FLSimpleStack. You can check also FLSimpleStackTest. Both are in Fuel repo.
Would something like that help for the stack in GemStone?
Yes. With this approach the resulting stack size is much smaller. To exceed the stack setting you need a graph containing an object which has a shortest route that is larger than the stack limit. And I think this is very unlikely :) I would need to change Sixx in order to have it work like this. I need to think about it. Thanks, Norbert
Norbert
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
On 03.06.2011 10:38, Norbert Hartl wrote:
Am 03.06.2011 um 10:20 schrieb Mariano Martinez Peck:
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that?
In 4. you said "while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.". So... Imagine object X with two instVars Y and Z. Imagine the method of the traverse is called #analye:. So you do #analize: X. Then, in such method you check whether X has regular pointers to regular objects, and if true, you analize them. So in this case you would send #analyze: Y and #analize: Z, generating the recursion. Right ?
yes, the time between analyse: Y and analyse: Z is dependent on the graph that is attached to Y. Y can have objects that have further objects etc. So leaving X aside, Y can be the first object to serialize and Z the last.
Ok, instead of sending #analyze: Y and #analize: Z what we do is just to do a push on a stack: #push: Y and #push: Z. And then we are done with X, so we pop from the stack and we continue with the next object (at some point in the future we will pop Y and Z) When there are no more objects in the stack it means we are done.
How do you write X? I can see that you turn a recursive trace into depth-by-depth trace but the difficult part is to write X. In X you need a reference representation of any sort. How do you derive that from the stack?
For performance (I think) we did FLSimpleStack. You can check also FLSimpleStackTest. Both are in Fuel repo.
Would something like that help for the stack in GemStone?
Yes. With this approach the resulting stack size is much smaller. To exceed the stack setting you need a graph containing an object which has a shortest route that is larger than the stack limit. And I think this is very unlikely :) I would need to change Sixx in order to have it work like this. I need to think about it.
Thanks,
Norbert
Yes, using a specific stack in the way Fuel does relies on there being a separate analyze-phase before the actual serialization of the objects. (Due to wanting to group objects of the same type together when serializing) You could do the same in a "raw" serializer like SIXX: 1) pop first object from stack 2) if any of the instvars need to be serialized, and their ids can not be found in cache, push self + missing objects 3) if not, serialize self, then goto 1 The key difference is you can not considering an object "done" during an analysis phase, but rather push it back on the stack for reevaluation after its dependencies have been handled in the same way. So the resulting stack will be much larger than what you see in Fuel, but still kept outside the call stack. Cheers, Henry
On 03.06.2011 11:00, Henrik Sperre Johansen wrote:
On 03.06.2011 10:38, Norbert Hartl wrote:
Am 03.06.2011 um 10:20 schrieb Mariano Martinez Peck:
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that?
In 4. you said "while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.". So... Imagine object X with two instVars Y and Z. Imagine the method of the traverse is called #analye:. So you do #analize: X. Then, in such method you check whether X has regular pointers to regular objects, and if true, you analize them. So in this case you would send #analyze: Y and #analize: Z, generating the recursion. Right ?
yes, the time between analyse: Y and analyse: Z is dependent on the graph that is attached to Y. Y can have objects that have further objects etc. So leaving X aside, Y can be the first object to serialize and Z the last.
Ok, instead of sending #analyze: Y and #analize: Z what we do is just to do a push on a stack: #push: Y and #push: Z. And then we are done with X, so we pop from the stack and we continue with the next object (at some point in the future we will pop Y and Z) When there are no more objects in the stack it means we are done.
How do you write X? I can see that you turn a recursive trace into depth-by-depth trace but the difficult part is to write X. In X you need a reference representation of any sort. How do you derive that from the stack?
For performance (I think) we did FLSimpleStack. You can check also FLSimpleStackTest. Both are in Fuel repo.
Would something like that help for the stack in GemStone?
Yes. With this approach the resulting stack size is much smaller. To exceed the stack setting you need a graph containing an object which has a shortest route that is larger than the stack limit. And I think this is very unlikely :) I would need to change Sixx in order to have it work like this. I need to think about it.
Thanks,
Norbert
Yes, using a specific stack in the way Fuel does relies on there being a separate analyze-phase before the actual serialization of the objects. (Due to wanting to group objects of the same type together when serializing)
You could do the same in a "raw" serializer like SIXX: 1) pop first object from stack 2) if any of the instvars need to be serialized, and their ids can not be found in cache, push self + missing objects 3) if not, serialize self, then goto 1
The key difference is you can not considering an object "done" during an analysis phase, but rather push it back on the stack for reevaluation after its dependencies have been handled in the same way.
So the resulting stack will be much larger than what you see in Fuel, but still kept outside the call stack.
Cheers, Henry Note: This never grows the call stack. You're basically left with:
[stack isEmpty ] whileFalse: [self serialize: stack pop] where serialize: either pushes objects and exits, or does actual serialization. Cheers, Henry
Am 03.06.2011 um 11:03 schrieb Henrik Sperre Johansen:
On 03.06.2011 11:00, Henrik Sperre Johansen wrote:
On 03.06.2011 10:38, Norbert Hartl wrote:
Am 03.06.2011 um 10:20 schrieb Mariano Martinez Peck:
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
In Fuel we traverse the graph but instead of using a recursion we use our own simple stack implementation where we push and pop objects. But Martin may explaint better than me.
I'm interested in details how it works and if it avoids the deep stack. Is there additional information about that?
In 4. you said "while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.". So... Imagine object X with two instVars Y and Z. Imagine the method of the traverse is called #analye:. So you do #analize: X. Then, in such method you check whether X has regular pointers to regular objects, and if true, you analize them. So in this case you would send #analyze: Y and #analize: Z, generating the recursion. Right ?
yes, the time between analyse: Y and analyse: Z is dependent on the graph that is attached to Y. Y can have objects that have further objects etc. So leaving X aside, Y can be the first object to serialize and Z the last.
Ok, instead of sending #analyze: Y and #analize: Z what we do is just to do a push on a stack: #push: Y and #push: Z. And then we are done with X, so we pop from the stack and we continue with the next object (at some point in the future we will pop Y and Z) When there are no more objects in the stack it means we are done.
How do you write X? I can see that you turn a recursive trace into depth-by-depth trace but the difficult part is to write X. In X you need a reference representation of any sort. How do you derive that from the stack?
For performance (I think) we did FLSimpleStack. You can check also FLSimpleStackTest. Both are in Fuel repo.
Would something like that help for the stack in GemStone?
Yes. With this approach the resulting stack size is much smaller. To exceed the stack setting you need a graph containing an object which has a shortest route that is larger than the stack limit. And I think this is very unlikely :) I would need to change Sixx in order to have it work like this. I need to think about it.
Thanks,
Norbert
Yes, using a specific stack in the way Fuel does relies on there being a separate analyze-phase before the actual serialization of the objects. (Due to wanting to group objects of the same type together when serializing)
You could do the same in a "raw" serializer like SIXX: 1) pop first object from stack 2) if any of the instvars need to be serialized, and their ids can not be found in cache, push self + missing objects 3) if not, serialize self, then goto 1
The key difference is you can not considering an object "done" during an analysis phase, but rather push it back on the stack for reevaluation after its dependencies have been handled in the same way.
So the resulting stack will be much larger than what you see in Fuel, but still kept outside the call stack.
Cheers, Henry
Note: This never grows the call stack. You're basically left with:
[stack isEmpty ] whileFalse: [self serialize: stack pop] where serialize: either pushes objects and exits, or does actual serialization.
Should we call it a "serialization trampoline" ?? :) Agreed, you can separate working stack and call stack to avoid the problem in total. thanks, Norbert
Hello Norbert, That sounds like a silly algorithm. There is no reason you should get a large stack size as long as you don't try to do this more than one level deep only: add object to the table repeat find first unwritten object in the table write its contents: simple objects direct for a complex object: look it up in the table if it doesn't exist, add it to the table write its reference mark as written until no unwritten objects in the table Stephan On 2 jun 2011, at 12:38, Norbert Hartl wrote:
Before I start a utility myself I want to ask if there is such a tool already.
I'm using sixx for object graph serialization. It works quite good but it has some severe limits. Well, the limits are not really sixx dependent but a problem of usual serializers. My problem is the size of the stack such a tool generates. Especially in gemstone this appears to be a problem because the stack is limited per gem.
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
Am 03.06.2011 um 20:29 schrieb Stephan Eggermont:
Hello Norbert,
That sounds like a silly algorithm. There is no reason you should get a large stack size as long as you don't try to do this more than one level deep only:
add object to the table repeat find first unwritten object in the table write its contents: simple objects direct for a complex object: look it up in the table if it doesn't exist, add it to the table write its reference mark as written until no unwritten objects in the table
It depends on what you like to call stupid. If it comes to algorithms we are talking about trade-offs to choose. Basically I think the way Sixx is working is fine but it has some troubles in gemstone. My approach keeps the way it is working but resorts the problematic case. Your approach is reversing referencing order. Thus preventing the ability to stream the result back in. You are exchanging backward references by forward references. This way you need to read in everything until you can reliably resolve references. With this approach you cannot utilize a pull parser which would enable you to read files of arbitrary size. This would like to prevent. But actually I wasn't looking for the smartest algorithm. I was asking for a tool that already exists. Norbert
On 2 jun 2011, at 12:38, Norbert Hartl wrote:
Before I start a utility myself I want to ask if there is such a tool already.
I'm using sixx for object graph serialization. It works quite good but it has some severe limits. Well, the limits are not really sixx dependent but a problem of usual serializers. My problem is the size of the stack such a tool generates. Especially in gemstone this appears to be a problem because the stack is limited per gem.
If I look at the serializers I know the mechanics are simple:
1. take an object and look it up in a table. 2. if the object is in the table it has already been serialized. We take a reference from the table and write it down 3. if it isn't in the table it means the object has not been serialized and the contents need to be written. After that the object with a reference representation is written to the table 4. while writing the content of an object (inst vars) I encounter simple objects (direct string representation) or complex objects (composite). In the latter case we start over from 1.
If the object graph is well interconnected than the odds are high that a stack will grow high until the first reference can be written. With a small pier kernel I already matched a stack size of 1000.
One solution I can think of is to collect and order objects in the graph that cover smaller segments from the overall graph. This way I would construct a sorted collection that is sorted by stack depth (lowest first). The collection will then be serialized leading to a much smaller possible stack sizes. It might be there are cases it can't work but I think for most cases it will do its job. And it will be slow of course.
Does anybody know if such a tool exists?
Norbert
participants (4)
-
Henrik Sperre Johansen -
Mariano Martinez Peck -
Norbert Hartl -
Stephan Eggermont