【问题标题】:Does using a delegate create garbage使用委托会产生垃圾吗
【发布时间】:2009-10-17 17:36:40
【问题描述】:

我正在使用 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 的类,然后我可以调用该接口上的方法,那不是不过真的很整洁!

【问题讨论】:

  • 委托应该非常小,并且在执行它们时不应产生大量垃圾。 (如果他们创建垃圾,那么每个人都应该是一个充满字节的拳头)。传递接口引用并且在完成后不使用它也会产生垃圾。

标签: c# delegates garbage-collection xna xbox360


【解决方案1】:

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

但是,在 XBOX 和其他紧凑型框架上,垃圾收集器运行得非常频繁,并且在创建新分配时运行得更频繁,所以是的,您也担心创建垃圾是正确的。你既要保持 live set 的小(以使收集便宜)又不要进行新的分配(因为这会触发收集。)

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

无论如何,如果您对内存性能有疑问,那么正确的做法是取出内存分析器并使用它来分析您的程序。胡乱猜测这是否恰好分配内存就像试图用指甲剪除草你的花园;这需要很多时间,并且实际上并没有实现您的目标。使用分析器分析您的表现并查看问题所在,然后修复它们。

【讨论】:

  • 我倾向于同意你的观点,而且我不太担心垃圾 - 我只是喜欢被告知!但是,您确定在 xbox 上就是这种情况,收集器是非世代相传的吗?
  • Xbox XNA GC 的性能与活/死对象的数量有关,所以是的,尽量减少对象的总数。是的,使用内存分析器来确定您是否真的有问题。
  • 实际上,在使用紧凑型 .NET CLR 的 XBOX 上,垃圾收集成本很高,因为每隔几秒收集一次垃圾会破坏游戏体验。 XNA XBOX 开发的常见做法是创建对象池或使用结构而不是类来避免集合。
  • @Olhovsky:你是对的;我会更新我的答案以澄清这一点。
【解决方案2】:

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

例如,这个:

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 延迟与活动对象的数量大致呈线性关系……然后再加上堆压缩的成本。我们的基准测试表明,深层对象层次结构与浅层对象层次结构之间的差异可以忽略不计,因此重要的是对象的数量。小物体也往往比大物体更便宜。

如您所见,尽量避免创建大量不必要的对象,您应该会做得更好。

【讨论】:

  • 我明白了,这很好知道,尽管尝试使用代理而不创建任何代理(加载时除外)会很有趣。
  • C# 中几乎任何东西都会以这种方式创建垃圾。我不建议避免使用短期引用。
  • 通常我也不会,但是在 Xbox 上,使用 XNA 平台,如果您可以推迟 GC(即减少运行频率)而不会产生不利影响,那确实是个好主意,因为如果过于频繁地发生,它导致游戏不连贯。因此,如果您想修复它,那么了解对 GC 有什么影响以及它何时发生是一个好主意。例如,在我展示的代码中,如果您经常多次运行该 ForEach 循环,那么也许您应该将委托存储在某个变量中,如果这不会改变行为的话。游戏优化通常不同于普通的桌面数据库应用。
  • 我知道这确实很旧,但是...如果您事先知道最终将为哪种方法创建委托(我假设是这种情况),然后将属性添加到拥有的对象该方法将委托返回给相关方法。用一个私有字段支持属性,或者懒惰地填充它,或者在构造对象时填充它。我相信您可以通过这种方式重用相同的委托实例(这样您就可以避免每次需要相关方法的委托时创建新的委托实例),因为委托是引用对象(编译器生成的类 - 正如其他人所指出的那样)。
【解决方案3】:

委托创建会产生垃圾,正如其他人已经指出的那样。

在您的示例中,使用 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)

【讨论】:

    【解决方案4】:

    是和不是。

    调用简单委托不会在堆上分配任何东西, 但创建委托会在堆上分配 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;
        }
    }
    

    输出如下,向右滚动查看Allocated 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 |           - |           - |
    
           - |                   - |
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-24
      相关资源
      最近更新 更多