Skip to content

Fix gc_realloc to expand in place #334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 5, 2014
Merged

Conversation

iabdalkader
Copy link
Contributor

dpgeorge added a commit that referenced this pull request Mar 5, 2014
Fix gc_realloc to expand in place
@dpgeorge dpgeorge merged commit 7bf724d into micropython:master Mar 5, 2014
@dpgeorge
Copy link
Member

dpgeorge commented Mar 6, 2014

Very nice!

@iabdalkader
Copy link
Contributor Author

thanks :)

@iabdalkader iabdalkader deleted the realloc branch March 11, 2014 14:14
@iabdalkader
Copy link
Contributor Author

I tried your example, before and after fixing n_free and checking for heap end, do you think there's still a bug here ? what am I missing ?
Note: the block in question is converted to upper case (eg H)

gc_realloc: allocating new block 
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhthhthttththtttt
0040: thhttHtthttttttthttth.......................h......h............
0080: ................................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhthhthttththtttt
0040: thhtt...httttttthttthHttt...................h......h............
0080: ................................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
Micro Python build <git hash> on 25/1/2014; F4DISC with STM32F405RG
Type "help()" for more information.
>>> l=[i for i in range(1000)];
gc_realloc: allocating new block 
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhthththtttt
0040: thhtthttth.hhhththhhhHttthttttttthttthttthh.hht....hhttttttt....
0080: ................................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhthththtttt
0040: thhtthttth.hhhththhhh....httttttthttthttthh.hht....hhtttttttHttt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: expanding 1 blocks (16 bytes) to 2 blocks (32 bytes)
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhttH...hhh........hht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhttHt..hhh........hht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: expanding 2 blocks (32 bytes) to 4 blocks (64 bytes)
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhttHt..hhh........hht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhttHttthhh........hht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: allocating new block 
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhttHttthhh........hht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhhHttttttthht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: allocating new block 
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhhHttttttthht.....................hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hhtHttttttttttttttt.....hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: allocating new block 
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hhtHttttttttttttttt.....hht....h........httt
0080: ttt.............................................................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHttttttttttttttttttttttttttttttt.............................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: expanding 32 blocks (512 bytes) to 64 blocks (1024 bytes)
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHttttttttttttttttttttttttttttttt.............................
00c0: ................................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
00c0: ttt.............................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: expanding 64 blocks (1024 bytes) to 128 blocks (2048 bytes)
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
00c0: ttt.............................................................
0100: ................................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
00c0: tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
0100: ttt.............................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
gc_realloc: expanding 128 blocks (2048 bytes) to 256 blocks (4096 bytes)
GC memory layout before:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
00c0: tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
0100: ttt.............................................................
0140: ................................................................
0180: ................................................................
01c0: ................................................................
GC memory layout after:
0000: hhhttthttthttthhhttthhhttthhhttthhhhhththhttttthhththhththththth
0040: hhhtt....hhh........hht.....................hht....h........httt
0080: tttHtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
00c0: tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
0100: tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
0140: tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
0180: ttt.............................................................
01c0: ................................................................
>>> 

@iabdalkader
Copy link
Contributor Author

new realloc
https://github.com/iabdalkader/micropython/blob/realloc/py/gc.c#L347

I changed the while loop to handle the case where the first HEAD block is the last one in the heap.

@dpgeorge
Copy link
Member

Ok, it looks better now, and the few tests I've run have all succeeded. Do you want to do a pull request, or I can just commit your code?

@iabdalkader iabdalkader mentioned this pull request Mar 12, 2014
@iabdalkader
Copy link
Contributor Author

what do you think about defragmenting the heap ? It will be slow, but it could be used as a last resort if gc_alloc fails to find memory ?

@dpgeorge
Copy link
Member

There is no way to defrag. You don't know for certain if a pointer is really a pointer....

@iabdalkader
Copy link
Contributor Author

I see, okay :)

@KeithJRome
Copy link

I am admittedly ignorant of the memory layout being used, but wouldn't valid object pointers always be resolvable to an instance? So you have four possibilities (I am making some general assumptions here):

  1. The pointer is null in which case it can be ignored.
  2. The pointer points to an "address" that is either not valid or falls outside of any "allocated" segments in the heap. In which case it can also be ignored.
  3. The pointer points to a valid address that falls within an allocated part of the heap (therefore it might be a valid object reference). In this case you follow the reference and see if it resolves to a real object. At that location you should find the object data structure which I presume starts with a type reference pointer. If that type reference pointer is valid then it is a real object that you need to be careful with.
  4. Same as Trivial fixes for building unix version #3 except you don't find the type pointer and therefore it is not really an object and can be ignored by compactor.

This assumes that object instances include a type pointer. How else will you know what the member vtable looks like otherwise?

Lots of assumptions, I know. But I'm not sure where in the codebase I might find the answers to whether those assumptions are valid. I'm just going by what I know from other GC and runtime typing implementations.

@dpgeorge
Copy link
Member

All your assumptions are correct.

The problem is that something which is not a pointer can still look like a pointer (eg an integer that just happens to have the right value) and then you will modify this integer to point to the relocated heap position. That's bad.

@iabdalkader
Copy link
Contributor Author

if I understand correctly, the problem is you will need to scan the stack/heap? and relocate pointers at some point ? at least this is how I imagine how it's done, then how do you know the difference between a pointer and an int ? example:

int  *array = gc_alloc(....)
int addr = (int) array;

I'm not sure where in the codebase I might find the answers to whether those assumptions are valid.

py/gc.c is what you looking for.

@KeithJRome
Copy link

But if you are only relocating managed objects, wouldn't integers (if they are typeless) or the internal data fields of integers (if they are first-class object types) naturally be excluded from scanning as a matter of fact?

@iabdalkader
Copy link
Contributor Author

you can't tell if it's a python object, there's been an issue about this to implement del, if you can tell python objects maybe it could work ?

@iabdalkader
Copy link
Contributor Author

#245

@KeithJRome
Copy link

I see. I wasn't following that conversation on del, but now I understand the issue.

The way this was solved in CLR was to cheat in this manner (well not exactly like this, but this is close enough):

  1. When you allocate a managed object instance, you ask for the size of object needed. It looks like mipy already does this. Let's call this size "N". It knows N because when you allocate the object, you tell the runtime what type it will be (and types always know how large they are).
  2. If this is a simple type then allocate N+1 bytes. In the first byte it writes a zero to indicate that this is a simple type (numbers, strings, etc - nonextensible primitive types). It returns a pointer to the second byte of the allocated block as the address of the object that was created.
  3. If this is a reference type then it allocates N+1+sizeof(pointer) bytes. In the first sizeof(pointer) bytes it writes a pointer reference to the type structure that describes this object. In the next byte it writes a 1 to indicate that this is a "reference type" which may contain pointers to other objects. It returns a pointer to an offset of 1+sizeof(pointer) in the allocated block as the address of the object that was created.
  4. Any code using that object doesn't need to care because as far as consumers are concerned, the object is the same as it was before. Consumers only have a pointer to the start of the actual object.
  5. The garbage collector can ignore any objects it finds that have a zero one byte prior to the object, because those are primitive types like numbers and other raw data. For all other objects it can read the pointer that precedes that marker to quickly find the type reference for the object.

This does have a downside of course. Compact arrays become problematic because an array of M elements is no longer sizeof(element)_M in size... it is now (sizeof(element)+sizeof(pointer))_M. You can get around this by making arrays themselves typed (CLR does this with generics) or by not allowing direct pointer arithmetic for array item access (access allowed only by index via accessor).

Individual pointer references to objects are fine, because they still point to the start of the object and are not aware of the extra few bytes that precede it.

@KeithJRome
Copy link

Also, with that type of approach you can most likely OR the pointer with the signal bit for reference types since you will realistically never define that many types and can ensure that type structures are always found in the first half of allocated memory.

@dpgeorge
Copy link
Member

@KeithJRome the approach you describe is good, although it can be optimised to use less RAM. Hopefully the Micro Python GC will have this "tagging" one day, so that objects can have their __del__ method called on deletion.

But it still doesn't solve the issue of garbage compacting, since any integer may look like a pointer and you don't want to rewrite any integers.

@dhylands
Copy link
Contributor

To do compacting you probably need to something like the original Macs did
(they had 128k or 512k RAM.

They used double pointers. So the user had a pointer to a pointer to the
heap object. The second pointer was managed by the heap so it could move
things around.

This adds a performance penalty with the benefit of being able to compact
the heap.

You could lock objects and use the direct pointer to get the performance
back.

Dave Hylands
Shuswap, BC, Canada
http://www.davehylands.com
On Mar 30, 2014 5:13 PM, "Damien George" notifications@github.com wrote:

@KeithJRome https://github.com/KeithJRome the approach you describe is
good, although it can be optimised to use less RAM. Hopefully the Micro
Python GC will have this "tagging" one day, so that objects can have their
del method called on deletion.

But it still doesn't solve the issue of garbage compacting, since any
integer may look like a pointer and you don't want to rewrite any integers.

Reply to this email directly or view it on GitHubhttps://github.com//pull/334#issuecomment-39045070
.

@pfalcon
Copy link
Contributor

pfalcon commented Mar 31, 2014

The biggest problem with double-pointers is not performance hit (there's already enough hit if using interpreter), but memory usage - extra pointer for each memory alloc. Intuitively, you need compaction for smaller heaps - they're likely to be more occupied and thus fragmented. But with smaller heaps, you don't have extra memory to spend on indirection pointers!

I personally wanted to implemented compacting heap when I hacked on Squirrel, but after I switched to uPy, I just enjoy minimal memory overhead a GC offers. With latest @dpgeorge changes of supporting fully static module definition, uPy on pyboards starts up with less than 1Kb heap usage, woohoo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants