Multithreaded C and garbage collection

BlitzMax Forums/BlitzMax Programming/Multithreaded C and garbage collection

JoshK(Posted 2009) [#1]
Would there be any advantage to defining objects in C code, creating them in C with a creation function, accessing the members in BlitzMax, and deleting them with a C function? Would this relieve the garbage collector from having to keep track of lots of objects?


plash(Posted 2009) [#2]
Would this relieve the garbage collector from having to keep track of lots of objects?
If you are still creating BlitzMax objects, no.


Brucey(Posted 2009) [#3]
How are you going to keep track of the C objects?


JoshK(Posted 2009) [#4]
A primitive linked list. For example, each entity would have a member for the next entity, and for its next sibling in the hierarchy. Then C or BMX could read the member/field and get the next one.


Otus(Posted 2009) [#5]
I doubt you can get it to work if you need to access and pass them around in BMX code. At least the single-threaded GC wouldn't know not to track them (i.e. increment and decrement reference counts). I'm not exactly sure about the MT one. On the other hand, if you only need to access them in C you'd be better of using just structs.

Edit:
Actually, I forgot you can create Extern Types, which basically let you do just that. Now whether there's a significant performance advantage, I know not.

tblah.bmx:


tblah.c:



JoshK(Posted 2009) [#6]
Ah, you can even do methods. I assume an Externed type is like a regular object, but with no GC performed on it?

This code demonstrates that in multithreaded mode, there is definitely a slowdown with more objects:
GCSetMode(2)

Type thing
	Field x,y,z,a,b,c,d,e,f,g,h
EndType

Local list:TList=New TList


Print "100 objects, 100 GCCollect calls"

For n=0 To 100
	list.addlast(New thing)
Next

time=MilliSecs()
For n=0 To 100
	GCCollect()
Next
Print (MilliSecs()-time)

list.clear()
GCCollect()


Print "1000 objects, 100 GCCollect calls"

For n=0 To 1000
	list.addlast(New thing)
Next

time=MilliSecs()
For n=0 To 100
	GCCollect()
Next
Print (MilliSecs()-time)

list.clear()
GCCollect()

End



Otus(Posted 2009) [#7]
Ah, you can even do methods. I assume an Externed type is like a regular object, but with no GC performed on it?

Yeah, they are basically just like C++ objects. If you use C instead of C++, the code to create methods becomes very messy very quick.


JoshK(Posted 2009) [#8]
C doesn't have methods.

My idea is that perhaps I can avoid some of the overhead of the multithreaded garbage collector if I use C++ classes for types that I am not going to be able to rely on the GC for anyways. It is totally redundant to use garbage collection on a type that requires a manual Free() method to be called.


Otus(Posted 2009) [#9]
C doesn't have methods.

Exactly. You probably wouldn't want to decipher the code where I have tried to fake polymorphism with C.

My idea is that perhaps I can avoid some of the overhead of the multithreaded garbage collector if I use C++ classes for types that I am not going to be able to rely on the GC for anyways. It is totally redundant to use garbage collection on a type that requires a manual Free() method to be called.

Please let us know if you get any results.


jpavel(Posted 2009) [#10]
The time taken by a precise GC (say, as opposed to a reference-counted GC that can't reclaim cycles) is largely determined by the size of the object graph it needs to traverse to look for live objects. So naturally a 1000 item list needs more time than a 100 item list. Even if your list contained pointers to external C objects, the GC would still have to walk all 1000 items.

Your Type "thing" doesn't have any reference fields, so the GC doesn't have to follow any links out of a "thing", and so in this case I can't imagine that switching from a native BlitzMax type to an external C type would yield any benefit.

Since we don't know the algorithm that the MS GC actually uses, I might be wrong though.

~Jesse


Otus(Posted 2009) [#11]
The time taken by a precise GC (say, as opposed to a reference-counted GC that can't reclaim cycles) is largely determined by the size of the object graph it needs to traverse to look for live objects. So naturally a 1000 item list needs more time than a 100 item list. Even if your list contained pointers to external C objects, the GC would still have to walk all 1000 items.


Walk, yes, but it doesn't have to descend into them for a recursive mark step, right?

Your Type "thing" doesn't have any reference fields, so the GC doesn't have to follow any links out of a "thing", and so in this case I can't imagine that switching from a native BlitzMax type to an external C type would yield any benefit.

Since we don't know the algorithm that the MS GC actually uses, I might be wrong though.


It's a mark-and-sweep algorithm. You can mostly find it in BlitzMax/mod/blitz.mod/blitz_gc_ms.c. From a cursory glance it actually doesn't seem to know about data types, so it would also follow Ints and Floats.


JoshK(Posted 2009) [#12]
Even if your list contained pointers to external C objects, the GC would still have to walk all 1000 items.

I don't believe C++ objects can be added to a list anyways, because they can't be cast to a Blitzmax Object type. You would have to store them in some kind of C++ structure or a special BlitzMax container like an array of byte pointers.

Of course this allows the possibility of an invalid pointer error, something which is impossible in pure BlitzMax, but our objective here is speed.

I think this demonstrates that a kind of GC-less object in BMX might be beneficial:

Type TThing {static}
Field a:Int
EndType

t:TThing=New TThing

t.Delete()
Print(t.a)'error occurs here!!!



JoshK(Posted 2009) [#13]
Here's another less drastic idea:

GCSetObjectMode(o:Object,mode:Int)

This can be used to remove an object from the object graph, when you know the object isn't going to be collected anyways. You can change the mode back when you are done with the object. Then there is still no risk of an invalid pointer, and you aren't clogging the GC graph up with objects that have to be manually freed anyways.

Type Thing

	Field parent:Thing
	Field kids:TList=New TList

	Method New()
		GCSetObjectMode(Self,False)
	EndMethod

	Method Free()
		If parent
			parent.kids.remove(Self)
			parent=Null
		EndIf
		For Local t:Thing=EachIn kids
			t.Free()
		Next
		GCSetObjectMode(Self,True)
	EndMethod

EndType

Local t:Thing=New Thing
t.Free()