Does using a delegate create garbage
Asked Answered
M

4

8

I'm working on a game for the xbox360, using XNA. On the Xbox the garbage collector performs rather badly compared to the one on a PC, so keeping garbage generated to a minimum is vital for a smoothly performing game.

I remember reading once that calling a delegate creates garbage, but now for the life of me can't find any references to delegates creating garbage. Did I just make this up or are delegates messy?

If delegates are messy, bonus points for suggesting a workaround.

public delegate T GetValue<T>(T value, T[] args);

public static T Transaction<T>(GetValue<T> calculate, ref T value, params T[] args) where T : class
{
    T newValue = calculate(value, args);
    return foo(newValue);
}

My code looks vaguely like that at the moment, the only solution I can think of to rid myself of delegates is to pass in a class which inherits an interface IValueCalculator, and then I can call the method on that interface, that's not really very neat though!

Menispermaceous answered 17/10, 2009 at 17:36 Comment(1)
Delegates should be really small and should not create a whole lot of garbage when you execute them. (if they create garbage it should be a fist full of bytes for each). Passing around an interface reference and not using that when you're done would also create garbage.Aragats
D
10

A delegate is itself an object, so if you create a delegate, perhaps for an anonymous method, and give this to some other method to execute, and don't store the delegate for future reference, then yes, that will produce garbage.

For instance, this:

collection.ForEach(delegate(T item)
{
    // do something with item
});

In this case, a new delegate object is created, but beyond the call to ForEach it is not referenced, and thus eligible for garbage collection.

However, calling delegates does by itself not produce garbage, any more so than calling any other method of the same type would. For instance, if you call a delegate that takes an Object parameter, passing in an Int32 value, this value will be boxed, but that would happen if you called a normal method the same way as well.

So using delegates should be fine, but excessive creation of delegate objects will be a problem.


Edit: A good article on memory management for Xbox and XNA is here: Managed Code Performance on Xbox 360 for XNA: Part 2 - GC and Tools. Pay attention to this quote:

So how does one control GC latency? Like NetCF for devices, the Xbox GC is non-generational. That means every collection is a full collection on the managed heap. Thus, we find that GC latency is approximately linear to the number of live objects… then add the cost of heap compaction on to that. Our benchmarks show that the difference between deep object hierarchies vs. shallow ones is negligible, so it’s mostly the number of objects that matter. Small objects also tend to be a somewhat cheaper to deal with than big objects.

As you can see, try to avoid creating lots of unnecessary objects, and you should fare better.

Denationalize answered 17/10, 2009 at 17:59 Comment(4)
I see, well that's good to know, although trying to use delegates without creating any (except at load time) is going to be interesting.Menispermaceous
Just about anything in C# creates garbage this way. I wouldn't advise to avoid the use of short-lived references.Trela
Normally I wouldn't either, but on the Xbox, with the XNA platform, if you can postpone GC (ie. make it run less often) without adverse effects, that's really a good idea, since it will make for choppy gameplay if it occurs too often. So being aware of what contributes to GC, and when it does it, is a good idea if you want to fix it. For instance, in the code I showed, if you're running that ForEach loop many times, often, perhaps you should store the delegate in a variable somewhere, if that doesn't change the behaviour. Game optimizations are usually different from normal desktop DB-apps.Denationalize
I know this is really old but... if you know in advance which method you will eventually be creating a delegate for (I'm assuming that case), then add properties to the object that owns that method that returns a delegate to the method in question. Back the property with a private field and either populate it lazily or at construction of the object. I believe you can re-use the same delegate instance this way (so you can avoid creating new ones every time you need a delegate to the relevant method) since delegates are reference objects (compiler-generated classes - as others have pointed out).Schizo
L
14

In the desktop environment garbage is effectively free. There what you want to worry about is how much non-garbage you are producing. Remember how the garbage collector works: it first marks all known objects, then it clears the mark on all live objects and compacts the live objects. The expensive step there is "unmark the live objects". Destroying the garbage is cheap; it's identifying the live objects that is expensive, and that cost depends on the number of live objects you have (and the complexity of their reference topology), not on the number of dead objects you have.

However, on XBOX and other compact frameworks, the garbage collector runs quite frequently and runs more often when new allocations are created, so yes, you are correct to worry about creating garbage too. You want to both keep the live set small (so as to make a collection cheap) and not make new allocations (because that triggers collections.)

Creating a delegate does allocate memory, but calling one is nothing more than calling a method named Invoke on a class. A delegate is not much more than a class with a method named Invoke that happens to immediately call another method when it is called.

Regardless, if you have a problem with memory performance then the right thing to do is to get out the memory profiler and use it to analyze your program. Casting about at random wondering if this or that happens to allocate memory is like trying to weed your garden with nail scissors; it takes a lot of time and doesn't actually accomplish your goals. Use a profiler to analyze your performance and see where the problems are, and then fix them.

Luxuriate answered 17/10, 2009 at 19:11 Comment(4)
I tend to agree with you, and I don't really worry about garbage a lot - I just like to be informed! However, are you certain this is the case on the xbox, where the collector is non generational?Menispermaceous
The performance of the Xbox XNA GC is relative to the number of live/dead objects, so yes, try to keep the total number of objects down. And yes, use a memory profiler to figure out if you really have a problem.Denationalize
Actually, on the XBOX, which uses the compact .NET CLR, garbage collection is expensive in the sense that collecting garbage every few seconds will ruin gameplay. Common practice with XNA XBOX development is to create object pools or to use structs instead of classes to avoid collections.Tortuosity
@Olhovsky: You are correct; I'll update my answer to clarify that.Luxuriate
D
10

A delegate is itself an object, so if you create a delegate, perhaps for an anonymous method, and give this to some other method to execute, and don't store the delegate for future reference, then yes, that will produce garbage.

For instance, this:

collection.ForEach(delegate(T item)
{
    // do something with item
});

In this case, a new delegate object is created, but beyond the call to ForEach it is not referenced, and thus eligible for garbage collection.

However, calling delegates does by itself not produce garbage, any more so than calling any other method of the same type would. For instance, if you call a delegate that takes an Object parameter, passing in an Int32 value, this value will be boxed, but that would happen if you called a normal method the same way as well.

So using delegates should be fine, but excessive creation of delegate objects will be a problem.


Edit: A good article on memory management for Xbox and XNA is here: Managed Code Performance on Xbox 360 for XNA: Part 2 - GC and Tools. Pay attention to this quote:

So how does one control GC latency? Like NetCF for devices, the Xbox GC is non-generational. That means every collection is a full collection on the managed heap. Thus, we find that GC latency is approximately linear to the number of live objects… then add the cost of heap compaction on to that. Our benchmarks show that the difference between deep object hierarchies vs. shallow ones is negligible, so it’s mostly the number of objects that matter. Small objects also tend to be a somewhat cheaper to deal with than big objects.

As you can see, try to avoid creating lots of unnecessary objects, and you should fare better.

Denationalize answered 17/10, 2009 at 17:59 Comment(4)
I see, well that's good to know, although trying to use delegates without creating any (except at load time) is going to be interesting.Menispermaceous
Just about anything in C# creates garbage this way. I wouldn't advise to avoid the use of short-lived references.Trela
Normally I wouldn't either, but on the Xbox, with the XNA platform, if you can postpone GC (ie. make it run less often) without adverse effects, that's really a good idea, since it will make for choppy gameplay if it occurs too often. So being aware of what contributes to GC, and when it does it, is a good idea if you want to fix it. For instance, in the code I showed, if you're running that ForEach loop many times, often, perhaps you should store the delegate in a variable somewhere, if that doesn't change the behaviour. Game optimizations are usually different from normal desktop DB-apps.Denationalize
I know this is really old but... if you know in advance which method you will eventually be creating a delegate for (I'm assuming that case), then add properties to the object that owns that method that returns a delegate to the method in question. Back the property with a private field and either populate it lazily or at construction of the object. I believe you can re-use the same delegate instance this way (so you can avoid creating new ones every time you need a delegate to the relevant method) since delegates are reference objects (compiler-generated classes - as others have pointed out).Schizo
D
1

Delegate creation generates garbage, as others already noted.

In your example, using params argument probably generates garbage as well.

Consider providing overloads without using params keyword.

That is the reason why overloads with different number of arguments usually exist in library methods along with the one using params keyword:

See String.Format Method (String, Object[])

Format Method (String, Object)
Format Method (String, Object[])
...
Format Method (String, Object, Object)
Format Method (String, Object, Object, Object)
Derogate answered 11/11, 2014 at 9:3 Comment(0)
C
1

Yes and no.

Calling simple delegate does not allocate any stuff on heap, but creating a delegate allocate 64 bytes on heap.

In order to avoid GC, you can precreate the delegate.

Let's verify it:

using BenchmarkDotNet.Running;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<BenchmarkDelegate>();            
        }
    }
}

Benchmark:

using BenchmarkDotNet.Attributes;

namespace Test
{
    [MemoryDiagnoser]
    public class BenchmarkDelegate
    {
        public delegate int GetInteger();

        GetInteger _delegateInstance;

        public BenchmarkDelegate()
        {
            _delegateInstance = WithoutDelegate;
        }

        [Benchmark]
        public int WithInstance() => RunDelegated(_delegateInstance);

        [Benchmark]
        public int WithDelegate() => RunDelegated(WithoutDelegate);

        public int RunDelegated(GetInteger del) => del();

        [Benchmark]
        public int WithoutDelegate() => 0;
    }
}

The output following, scroll right to see Allocated Memory/Op column:

DefaultJob : .NET Core 2.2.1 (CoreCLR 4.6.27207.03, CoreFX 4.6.27207.03), 64bit RyuJIT
|          Method |       Mean |     Error |    StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|---------------- |-----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
|    WithInstance |  7.5503 ns | 0.0751 ns | 0.0702 ns |           - |           - |           - |                   - |
|    WithDelegate | 35.4866 ns | 1.0094 ns | 1.2766 ns |      0.0203 |           - |           - |                64 B |
| WithoutDelegate |  0.0000 ns | 0.0000 ns | 0.0000 ns |           - |           - |

       - |                   - |
Carbine answered 25/2, 2019 at 5:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.