使用委托创建垃圾

时间:2009-10-17 17:36:41

标签: c# delegates garbage-collection xna xbox360

我正在使用XNA为xbox360开发游戏。在Xbox上,与PC上的垃圾收集器相比,垃圾收集器的性能相当差,因此将生成的垃圾保持在最低限度对于顺畅执行游戏至关重要。

我记得曾经读过一次调用委托创建垃圾,但现在我的生活中找不到任何对委托创建垃圾的引用。我是刚刚解决这个问题还是代理人搞乱了?

如果代表们处于凌乱状态,可以提出建议解决方案的奖励积分。

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);
}

我的代码看起来模糊不清,我能想到摆脱委托的唯一解决方案是传入一个继承了接口IValueCalculator的类,然后我可以在该接口上调用该方法,这不是真的很整洁!

4 个答案:

答案 0 :(得分:14)

在桌面环境中垃圾实际上是免费的。您要担心的是您正在制作多少非垃圾。记住垃圾收集器的工作原理:它首先标记所有已知对象,然后清除所有活动对象上的标记并压缩活动对象。那里昂贵的一步是“取消标记活动对象”。摧毁垃圾很便宜;它识别昂贵的实时对象,并且成本取决于您拥有的活动对象的数量(以及它们的参考拓扑的复杂性),而不是您拥有的死对象的数量。

然而,在XBOX和其他紧凑的框架上,垃圾收集器运行频繁并且在创建新分配时运行得更频繁,所以是的,您也可以担心创建垃圾。您希望将实时集保持为较小(以便使集合便宜)并且不进行新分配(因为这会触发集合。)

创建委托确实会分配内存,但调用只是在类上调用名为Invoke的方法。委托只不过是一个带有名为Invoke的方法的类,它在调用时会立即调用另一个方法。

无论如何,如果你的内存性能有问题,那么正确的做法是拿出内存分析器并用它来分析你的程序。随意想知道这个或那个是否会分配记忆就像试图用指甲剪掉你的花园一样;这需要花费很多时间,并没有真正实现你的目标。使用分析器分析您的性能并查看问题所在,然后进行修复。

答案 1 :(得分:10)

委托本身就是一个对象,所以如果你创建一个委托,也许是为了一个匿名方法,并将其赋予其他一些方法来执行,并且不存储委托以供将来参考,那么是, 会产生垃圾。

例如,这个:

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

在这种情况下,会创建一个新的委托对象,但在调用ForEach之后,它没有被引用,因此有资格进行垃圾回收。

然而,调用委托本身并不会产生垃圾,而是调用相同类型的任何其他方法。例如,如果您调用带有Object参数的委托,并传入Int32值,则此值将被加框,但如果您以同样的方式调用普通方法,则会发生这种情况。

因此使用委托应该没问题,但是过多创建委托对象将是一个问题。


编辑:有关Xbox和XNA内存管理的好文章在这里:Managed Code Performance on Xbox 360 for XNA: Part 2 - GC and Tools。注意这句话:

  

那么如何控制GC延迟呢?与设备的NetCF一样,Xbox GC是非代数的。这意味着每个集合都是托管堆上的完整集合。因此,我们发现GC延迟与活动对象的数量大致呈线性关系...然后将堆压缩的成本添加到其上。我们的基准测试表明深层对象层次结构与浅层对象层次结构之间的差异可以忽略不计,因此它主要是重要的对象数量。与大型物体相比,小物体处理起来也要便宜一些。

正如你所看到的,尽量避免创建大量不必要的对象,你应该做得更好。

答案 2 :(得分:1)

代理创建会产生垃圾,正如其他人已经注意到的那样。

在您的示例中,使用params参数可能也会生成垃圾。

考虑在不使用params关键字的情况下提供重载。

这就是为什么库方法中通常存在具有不同数量参数的重载以及使用params关键字的重载的原因:

请参阅String.Format Method (String, Object[])

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

答案 3 :(得分:1)

是,不是。

调用简单委托不会在堆上分配任何东西, 但是创建一个委托会在堆上分配64个字节。

为了避免GC,您可以预先创建委托。

让我们验证一下:

using BenchmarkDotNet.Running;

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

基准:

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;
    }
}

下面的输出,向右滚动以查看 Allocation Memory / Op 列:

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 |           - |           - |

       - |                   - |