使用委托创建垃圾

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用委托创建垃圾相关的知识,希望对你有一定的参考价值。

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

答案

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

例如,这个:

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

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

另一答案

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

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

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

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

另一答案

其他人已经注意到,委托创建会产生垃圾。

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

是的,不是。

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

       - |                   - |

以上是关于使用委托创建垃圾的主要内容,如果未能解决你的问题,请参考以下文章

类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们的问题的解决方法

对“XXX::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们

已在 C# 中对垃圾收集委托进行了调用?

JAVA的事件委托机制和垃圾回收机制

iOS UIPopoverController委托不工作,仍然是零

创建委托 LdapAuthenticationProvider 而不使用 xml 配置,而不是通过 java 代码