Garbage collectors suck for games

Discussion in 'Rendering Technology and APIs' started by Frank, Sep 24, 2010.

  1. Frank

    Frank Certified not a majority
    Veteran

    Joined:
    Sep 21, 2003
    Messages:
    3,187
    Likes Received:
    59
    Location:
    Sittard, the Netherlands
    Yes, I know, I've encountered many things like that as well. It might be better to put it like this:

    "First figure out the lifecycle of objects and write the code to create, handle and dispose them, before actually using them."

    There are lots of border cases, but I tend to always create a manager for each (class of) object(s). And yes, COM objects (or in general everything using an interface), a factory or library that isn't very specific in who creates/owns/disposes anything is a pain to manage.
     
  2. Frank

    Frank Certified not a majority
    Veteran

    Joined:
    Sep 21, 2003
    Messages:
    3,187
    Likes Received:
    59
    Location:
    Sittard, the Netherlands
    The global .NET class hierarchy doesn't have a property to record which object created an object, it uses reflection instead.

    Which is a big pain, as you first have to figure out which component model it resides in (for example, WPF uses a different one than plain .NET), which object hierarchy should be used to track the owner (WPF uses multiple ones, depending), which class it actually is (that's easy), cascade through all the base classes until you find one you recognize (as bad as it sounds). And all the different hierarchies have different functions/owner class methods to do that!

    A simple property that stores the owner, and a simple check if one of it's base classes is the supplied one would solve a lot of problems.

    I created my own object hierarchy from scratch for a project that had a class hierarchy of close to 100 classes, many of them (close to) equal to an existing .NET class, simply because it was too complex to track the hierarchy.

    And they had to be serialized, which is a big problem as well (same reason). I ended up writing my own enumerator and serializer for all of them as well.

    Well, that pretty much breaks down completely if you're using factories, COM, WCF, WPF and the other new additions. Because in that case, the type checking is done at RUNTIME, through reflection. Which means: you have to do it yourself, if you don't want your program to break.

    I think the .NET model needs it. :)

    Agreed. I do the same as well.

    Yes, and that works well, unless you're trying to call unmanaged code. Even worse: the declaration of that unmanaged function tends to depend on the .NET version used.

    Or writing and calling a dispose method for anything that even touches things like handles ;).

    At that time, the GC only initialized when less than 20% of system memory was free, and it did so at it's leisure (ie: slowly). And ADO.NET was very dynamic: it created/freed any single resource it needed. (MS SQL Reporting Services is written in C#.)

    When the total amount of memory used by the .NET runtime exceeded the total amount of RAM available, Windows killed it.
     
  3. Richard

    Richard Mord's imaginary friend
    Veteran

    Joined:
    Jan 22, 2004
    Messages:
    3,508
    Likes Received:
    40
    Location:
    PT, EU
    Thanks, I understand now. Let me ask you why exactly do you need to know which instance created which other instance? Which problems would it solve? I recognise the value in determining object life-time, etc. but aside from plumbing what are the advantages you are thinking about. See below, calling unmanaged code.

    Hate COM. But WCF, WPF, even WinForms in some places are highly abstract so some measure of dynamic type validation must happen.

    Sure, no argument there but I doubt any other framework of this size and capability is much better heh. Btw, have you looked at .NET 4 in this regard?

    I haven't had many opportunities/need to employ P/Invoke and marshalling. Mostly because any time I have to look at MFC and even SDK code I get flashbacks to the bad c++ days. I mostly use it for the 7/Vista bling-bling MS keeps leaving out of WinForms. Since you need lower level access, you'll definitely require more flexibility and more information on the object tree.

    I take it you don't like having to debug why your program crashes reading from disk with a null exception only 10% of the time. :twisted: Dispose is Chaotic Evil. I'd rather have objects taking up memory, than trying to subvert the GC and blowing up in my face!

    Physical RAM? How odd.
     
  4. ERP

    ERP
    Veteran

    Joined:
    Feb 11, 2002
    Messages:
    3,669
    Likes Received:
    49
    Location:
    Redmond, WA
    Just anote on the .net GC, unmanaged interop and memory usage.

    My biggest problem is the totally passive way it does collection, it will wait until it gets within some threshold of the limit before freeing objects.

    You can actually restrict the maximum ammount of memory the .Net runtime will use, but you have to write a host. It's <100 lines of C++.

    If the runtime was killed it was because it couldn't allocate enough memoy for the "Out Of Memory" exception to be thrown. This means that the GC could not free any memory, so you had a leak.

    If you're making calls into unmanaged code, especially through COM interop with pinned memory, it's extremely easy to write leaks, they are extremely hard to track down.

    Having had to write COM wrappers for external libraries, it's astonishing to me how PInvoke just works most of the time.
     
  5. EduardoS

    Newcomer

    Joined:
    Nov 8, 2008
    Messages:
    131
    Likes Received:
    0
    Except it doesn't happen, the OutOfMemoryException is singleton it won't fail to be thrown, any unhandled exception will cause Windows to terminate it, even an OutOfMemoryException in the handler of a OutOfMemoryException wich tried to allocate a new object.
     
  6. N00b

    Regular

    Joined:
    Mar 11, 2005
    Messages:
    698
    Likes Received:
    114
    Introducing a global parent property would be highway one into leak hell. In order to have an object safely collected by the GC you would have to delete all references to that object and null the owner property. As long as ownership is clear that might not be a problem, but when objects start getting passed around between domain code, framework code and third-party libraries things would get ugly in no time. And I haven't even mentioned the non-obvious cases like anonymous delegates that get passed around. So, sorry, but dumb idea.

    And what do you mean with "it uses reflection instead"? Where does .NET use reflection in order to find an owner object?

    Sounds like a leak to me.

    You can actually influence (lower) the threshold by calling GC.AddMemoryPressure(). It's far from perfect, but it's a start.
     
  7. Frank

    Frank Certified not a majority
    Veteran

    Joined:
    Sep 21, 2003
    Messages:
    3,187
    Likes Received:
    59
    Location:
    Sittard, the Netherlands
    Exactly. That's the whole idea abut ownership, and is exactly what you want to happen, unless you have a very weird idea about object/memory management.

    So, sorry, but dumb idea.

    :D

    Well, everywhere and all the time? Even the GC uses it.

    Well, it happened with the standard MS SQL Reporting Services, as written by Microsoft themselves. I tried to patch things up to stop it from happening, but had only little success.

    So, if it's a leak, it was as intended (TM), or not mine.

     
  8. Graham

    Graham Hello :-)
    Veteran Subscriber

    Joined:
    Sep 10, 2005
    Messages:
    1,480
    Likes Received:
    210
    Location:
    Bend, Oregon
    UnrealScript? :mrgreen:

    There is no denying you have to treat a garbage collected language differently.

    All my experience with .net is that memory isn't your problem unless you have done something badly wrong, whereas (for example) managed<->unmanaged transitions will kill performance.
     
  9. Frank

    Frank Certified not a majority
    Veteran

    Joined:
    Sep 21, 2003
    Messages:
    3,187
    Likes Received:
    59
    Location:
    Sittard, the Netherlands
    I forgot to respond, so here it is:

    The specific project I was talking about, was a .docx report generator. There are many ways to create something like that, but as the OfficeXML standard is a very loose one, that greatly depends on it's intepretation on the type and state of the elements around it (many different state machines and containers that can contain a variety of children, from many different layers in the model), it wasn't feasible to simply iterate through a description of the document, and I needed an object (class) structure that took all those discrepancies into account.

    Which is alike the object hierarchy you want for many other projects, like games, where you need to be able to assume that every object (instance) also manages all their children, no matter what type they are (like, from a simple sprite up to a flow shader).

    First you have to build your abstract model, be it a document or game scene, then you have to provide manipulators (methods) to insert the actual data, which can be used through the UI. And at the highest level, you want something like an "execute" method, that creates the actual draw calls, document or database manipulations.

    In such a model, it is paramount that all the object (instance) management happens in a strictly top-down way, and completely transparent to the higher levels. Which requires that you can create, fill, use and dispose a variety of children through a ripple-down effect.

    But the other direction is equally important: a child has to be able to communicate with it's parent, for status changes as well as handling exceptions (which can be quite mundane and not the "raising" kind).



    If you want to scale your application over multiple threads/processors/servers, there are basically two models: thread spawning (which requires shared memory, something you really don't want if you can prevent it), or job spawning/stream processing, in which you create independent jobs that go and execute somewhere and sometime convenient. All of this also requires the same up- and downward object interactions.


    Or, in short: you need to be able to keep track of your instance hierarchy, and be able to talk with children, parents and siblings. So you can simply fill it up with data, and call "execute", after which all actions needed ripple through the model and serialize and output all data as needed.
     
    #49 Frank, Oct 24, 2010
    Last edited by a moderator: Oct 24, 2010
  10. EduardoS

    Newcomer

    Joined:
    Nov 8, 2008
    Messages:
    131
    Likes Received:
    0
    Is that dificult to pass "this" as a parameter during object creation?

    I don't know how complex .docx are (other say they are ridiculous complex tought) and even if there is the need for children objects to comunicate to parents by means other than return values and exceptions, letting the children to have a reference to a parent is something I usually avoid for several reasons and so I see several reason to this not be the default behavior.
     
  11. Otto Dafe

    Regular

    Joined:
    Aug 11, 2005
    Messages:
    400
    Likes Received:
    59
    It could perhaps be more explicit, but I would assume that returning a smart pointer is handing off ownership (or at least offering it), returning a ref is not. I think the semantic suggestion here is probably just as valuable as the functionality.
     
  12. ERP

    ERP
    Veteran

    Joined:
    Feb 11, 2002
    Messages:
    3,669
    Likes Received:
    49
    Location:
    Redmond, WA
    The issue is that returning a smart pointer often isn't handing off ownership, it's just returning a smart pointer. Any none trivial set of structures will usually result in smart pointers creating ref loops, because they tend to be used as safe references in classes rather than actual indicators of strong ownership.

    With garbage collectors there is no real delineation between strong ownership and just references to the object, because ref loops don't generally cause leaks (though it's quite possible to write leaks). I do agree that it's still important to understand ownership at a semantic level even in a GC language.

    I personally work around the ownership issue in C/C++ by using API conventions, if a function takes a ** to a class as an output it's assumed the caller is responsible for cleanup, otherwise the creator is responsible for cleanup.

    I've wasted more hours of my life cleaning up dangling pointer bugs than I would care to count, it's too easy a bug to write, has often totally random symptoms and often extremly difficult to find.
     
  13. EduardoS

    Newcomer

    Joined:
    Nov 8, 2008
    Messages:
    131
    Likes Received:
    0
    How?
     
  14. ERP

    ERP
    Veteran

    Joined:
    Feb 11, 2002
    Messages:
    3,669
    Likes Received:
    49
    Location:
    Redmond, WA
    Dangling references that trace back to top level references.
     
  15. EduardoS

    Newcomer

    Joined:
    Nov 8, 2008
    Messages:
    131
    Likes Received:
    0
    And how this would happen? I mean, in a decent GC?
     
  16. ERP

    ERP
    Veteran

    Joined:
    Feb 11, 2002
    Messages:
    3,669
    Likes Received:
    49
    Location:
    Redmond, WA
    No it's not a GC error, it's a user error.
    The classic one is someone creates a manager class of some sort, and never removes things from the manager. The manager retains a ref, even though the resource is no longer in use. You can argue whether it's the same sort of leak or not. But the effect is the same, something that is no longer in use hangs around indefinitely.
     
  17. Frank

    Frank Certified not a majority
    Veteran

    Joined:
    Sep 21, 2003
    Messages:
    3,187
    Likes Received:
    59
    Location:
    Sittard, the Netherlands
    That's what I did, but to be able to do that I had to create my own class hierarchy from scratch, as very many things in .NET require constructors without parameters, and most default classes have no property that can be used to store that pointer (and some are non-inheritable).

    That's an understatement :)

    But it's more a general problem, if you want to store object references that have no common (inherited) parent that supplies the methods and properties needed.

    Best then to start from scratch with a base class that does.

    Agreed.

    It depends on how you want to use those classes. If you mostly use them as "smart" functions, there is no need. But if you want to use them to build an abstract representation or model of something, you need all the cogs to be able to give feedback.
     
  18. Otto Dafe

    Regular

    Joined:
    Aug 11, 2005
    Messages:
    400
    Likes Received:
    59
    I'm clearly missing some nuance of the discussion here, but why not dynamic_cast in this situation? You are trying to ref the parent, correct?
     
  19. hkultala

    Regular

    Joined:
    May 22, 2002
    Messages:
    297
    Likes Received:
    38
    Location:
    Herwood, Tampere, Finland
    About garbage collection and performance:

    1) Reference counting has big performance overhead
    Every time you are taking new refs to object or removing ref, you have overhead. When doing function calls with the obejcts you practically always do it. And as the overhead of removing a ref includes if (refcount == 0) brach, it's much worse for performance than simple update of value.

    And if you try to use ref counting with objects that were not designed for it(not derived from some recountable base class/template), you have to add an additional intermediate layer which means you have 2-level memory references which makes those objects slower to use.

    2) generational garbage collection makes memory allocation much faster.
    If you have a generational garbage collection, malloc() is just one addition and comparison (actually the whole malloc overhead is same as the overhead of removing reference when ref counting)

    3) There are real-time garbage collectors.
    If you are using real-time garbage collector, there are NONE of those "GC pauses" people seem to be so afraid of.
     
  20. hkultala

    Regular

    Joined:
    May 22, 2002
    Messages:
    297
    Likes Received:
    38
    Location:
    Herwood, Tampere, Finland
    Fight slight slowdown with much bigger slowdown?
     
Loading...

Share This Page

  • About Us

    Beyond3D has been around for over a decade and prides itself on being the best place on the web for in-depth, technically-driven discussion and analysis of 3D graphics hardware. If you love pixels and transistors, you've come to the right place!

    Beyond3D is proudly published by GPU Tools Ltd.
Loading...