About Monkey 2 › Forums › Monkey 2 Programming Help › Structs and Garbage Collection
Tagged: garbage collection structs
This topic contains 10 replies, has 4 voices, and was last updated by
codifies
2 years, 3 months ago.
-
AuthorPosts
-
December 27, 2016 at 12:54 pm #6047
As I understand Structs (which is not very well btw), they are in essence a collection of primitives and pointers.
So in (my) theory creating (a lot of) structs should not constitute a problem in terms of garbage collection.
My concern is that I have entities that will require hit-testing with the ground each “Update()”. And the way I have currently implemented the hittesting it uses Rectf structs. Something like this:
Monkey123456789101112131415Class EntityField x:FloatField y:FloatField w:FloatField h:FloatMethod ToRectf:Rectf()Return New Rectf(x, y, x+w, y+h)End' This gets called every UpdateMethod HitCheck(area:Rectf)SquareHitTest( ToRectf(), area )EndEndMy concern is that “ToRectf” is creating a new “Rect” struct each update never to be used again, so it somehow needs to be removed (if Rects were objects then it would be an issue).
If the struct values get interpreted as part of the call stack then that’s fine. But if it gets Garbage Collected (ie. during runtime the application has to check if there are instances of it elsewhere in the program) then that may cause unnecessary load and I’d be better off just passing the actual primitive float values to the HitTest function.
I’m not sure if I explained the problem well. tl;dr I am mostly wondering how the Monkey-Compiler interprets Structs and whether Structs need to be garbage collected or not.
December 28, 2016 at 3:07 am #6064Using search in main forums page I found the same topic with lot info.
http://monkey2.monkey-x.com/forums/topic/structs-and-the-gc/
December 28, 2016 at 9:10 am #6067a wiki would be great for little nuggets like this…
December 28, 2016 at 9:42 am #6071Thanks! I suppose it always pays to check the docs and forums.
So creating a struct and passing it around instead of primitives is basically the same as both the primitives and structs are “pass by value”.
December 28, 2016 at 10:43 am #6072So creating a struct and passing it around instead of primitives is basically the same as both the primitives and structs are “pass by value”.
Yep. And using Structs is generaly a bit faster than classes as it is put on the stack and not on the heap.
December 28, 2016 at 12:47 pm #6075err careful there, passing a reference to a class could be a lot faster than copying (passing by value) a struct, especially if you’re structs got more than a few members…
Of course you can always pass a pointer to a struct….
December 28, 2016 at 1:53 pm #6085I think for my specific case I’d rather be creating structs than objects since they are a one time use thing and creating objects would drastically increase the load on the GC, right?
I am just using “Rect” structs as a more convenient way to pass primitives (i.e. coordinates and dimensions for hit testing) and just wanted to make sure I am not making my game terribly inefficient in the process.
December 28, 2016 at 4:39 pm #6086In my experience the order of fastness is:
1-primitive
2-struct field
3-struct propertyso avoid properties if it is not necessary
I played along with optimisation in this example: https://github.com/abakobo/learn_monkey2/tree/master/julia
The most optimised version I could get is not using structs because primitives where faster..
In the struct_non_optim example you can try 3 different manners to use structs with 3 very different speeds (by uncommenting/commenting). Field being the fastest one.And here a little bit of code to illustrate the speed difference between struc and class with a simple object case:
[/crayon]Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107[crayon-5cba9c9f7c15e739282834 inline="true" ]#Import "<std>"Using std..Struct structorField x:IntField y:IntMethod New(a:Int,b:Int)x=ay=bEndMethod Add:Int()Return x+yEndEndClass classorField x:IntField y:IntMethod New(a:Int,b:Int)x=ay=bEndMethod Add:int()Return x+yEndEndFunction adds:int(a:Int,s:structor)Return a+s.Add()EndFunction addc:Int(a:Int,c:classor)Return a+c.Add()EndGlobal ii:=2000Global jj:=2000Function Main()Local t:IntLocal ta:IntLocal add:IntLocal c:classorLocal s:structorta=Millisecs()For Local j:=0 until jjFor Local i:=0 until iis=New structor (i+1,j+1)add=s.Add()Repeatadd=add+s.Add()Until add>2000000NextNextt=Millisecs()-taPrint "time struct: "+tta=Millisecs()For Local j:=0 Until jjFor Local i:=0 Until iic=New classor (i+1,j+1)add=c.Add()Repeatadd=add+c.Add()Until add>2000000NextNextt=Millisecs()-taPrint "time class: "+tta=Millisecs()For Local j:=0 until jjFor Local i:=0 until iis=New structor (i+1,j+1)add=s.Add()Repeatadd=adds(add,s)Until add>2000000NextNextt=Millisecs()-taPrint "time struct tru func (by value): "+tta=Millisecs()For Local j:=0 Until jjFor Local i:=0 Until iic=New classor (i+1,j+1)add=c.Add()Repeatadd=addc(add,c)Until add>2000000NextNextt=Millisecs()-taPrint "time class tru func (by ref): "+tEndDecember 28, 2016 at 8:20 pm #6089Damn you’re right parameters are much faster than structs! A lot faster!
I even made my own test to check the speed difference for a similar implementation to the one in my game, and primitives values are significantly faster. As I understand it this has to do with the time it takes to copy structs from one method stack to the next.
I also found that using Getters to get the primitives also significantly affects the time it takes. I assume this is also to do with copying the (“pass-by-value”) value from the one method stack to the next as opposed to just reading it directly from the class’ memory in the heap.
I may have to consider changing my game to just passing the primitives separately (instead of combining them inside a struct) since the performance impact is so much more than I anticipated.
Thanks for the heads up!
<!–more–>
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#Import "<std>"Using std..' Calculate distance between two coordinatesFunction CalculateDistance:Float(pos1:Vec2f, pos2:Vec2f)Return CalculateDistance( pos1.X, pos1.Y, pos2.X, pos2.Y )EndFunction CalculateDistance:Float(x1:Float, y1:Float, x2:Float, y2:Float)Local i := x1-x2Local j := y1-y2Return Sqrt(i*i + j*j)End' Entity inside the "world" has x and y fieldsClass EntityField x:FloatField y:Float' entity is given a random coordinate in the worldMethod New()Self.x = Rand()Self.y = Rand()End' Get Distance from Self to another entitiy in the "world"Method DistStruct(e:Entity)CalculateDistance(Self, e) ' passing Vec2f structs with coordinatesEndMethod DistStructGet(e:Entity)CalculateDistance(GetVec2f(), e.GetVec2f()) ' passing Vec2f structs with coordinatesEndMethod DistPrimsGet(e:Entity)CalculateDistance(GetX(), GetY(), e.GetX(), e.GetY()) ' passing primitive coordinate valuesEndMethod DistPrims(e:Entity)CalculateDistance(x, y, e.x, e.y) ' passing primitive coordinate valuesEndMethod GetX:Float()Return xEndMethod GetY:Float()Return yEndMethod To:Vec2f()Return New Vec2f(x, y)EndMethod GetVec2f:Vec2f()Return New Vec2f(GetX(), GetY())EndEnd' The test involves calculating distances from each entity (in an array of entitties) to every entity'>in the same array.Const n := 5000 ' size of entitiy arrayFunction Main()Local ents:Entity[] = New Entity[n]For Local i:=0 Until nents[i] = New Entity()EndLocal t := Millisecs()'' CREATE AND PASS STRUCT TO DISTANCE CALC. FUNCTION ''For Local i:=0 Until nFor Local j:=0 Until nents[i].DistStruct(ents[j])EndEndPrint "Time DistStruct = " + (Millisecs()-t)t = Millisecs()'' CREATE AND PASS STRUCT (created using getters) TO DISTANCE CALC. FUNCTION ''For Local i:=0 Until nFor Local j:=0 Until nents[i].DistStructGet(ents[j])EndEndPrint "Time DistStruct (w/ getter) = " + (Millisecs()-t)t = Millisecs()'' PASS PRIMITIVES TO DISTANCE CALC. FUNCTION ''For Local i:=0 Until nFor Local j:=0 Until nents[i].DistPrims(ents[j])EndEndPrint "Time DistPrims = " + (Millisecs()-t)t = Millisecs()'' PASS PRIMITIVES (from getter) TO DISTANCE CALC. FUNCTION ''For Local i:=0 Until nFor Local j:=0 Until nents[i].DistPrimsGet(ents[j])EndEndPrint "Time DistPrims (w/ getter) = " + (Millisecs()-t)EndFunction Rand:Float()Return Rnd(100)EndDecember 29, 2016 at 4:05 am #6092One note about optimization: if your game works at desired fps you may don’t care about optimization if that brings difficult code styling.
December 29, 2016 at 9:51 am #6093…and benchmarking, in the time it takes to test a few thousand calls, your PC could be doing something else, to stop doing it just in time for the next test, benchmarking is fraught potential pit falls…
-
AuthorPosts
You must be logged in to reply to this topic.