8

我正在使用 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

4 回答 4

14

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

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

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

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

于 2009-10-17T19:11:47.933 回答
10

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

例如,这个:

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

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

但是,调用委托本身不会产生垃圾,这与调用任何其他相同类型的方法一样。例如,如果你调用一个带Object参数的委托,传入一个Int32值,这个值将被装箱,但如果你以同样的方式调用普通方法,也会发生这种情况。

所以使用委托应该没问题,但是过多创建委托对象会是个问题。


编辑:这里有一篇关于 Xbox 和 XNA 内存管理的好文章:XNA 的Xbox 360 上的托管代码性能:第 2 部分 - GC 和工具。注意这个引用:

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

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

于 2009-10-17T17:59:10.647 回答
1

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

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

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

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

请参见String.Format 方法 (String, Object[])

Format Method (String, Object)
Format Method (String, Object[])
...
Format Method (String, Object, Object)
Format Method (String, Object, Object, Object)
于 2014-11-11T09:03:07.957 回答
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;
    }
}

以下输出,向右滚动以查看 分配的内存/操作列:

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

       - |                   - |
于 2019-02-25T05:48:09.760 回答