为什么不仅在实际需要时创建闭包帮助程序实例 (DisplayClass)?

Why aren't closure helper instances (DisplayClass) only created when actually needed?

提问人:mike 提问时间:10/13/2022 最后编辑:trincotmike 更新时间:10/17/2022 访问量:60

问:

我有一个关于关闭和堆分配的问题。请考虑以下代码:

//ORIGINAL CODE, VERSION 1
public class Program
{
    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
    
    public object Test(String x){
        
        if(x == "abort") return null;
        
        return _coll.GetOrAdd(x, (k)=> TestCallback());
    }
    
    public static object TestCallback() => null;
}

在回调函数中使用。而且,根据 https://sharplab.io,这被降低到(缩写):Teststatic

//LOWERED CODE, VERSION 1
public class Program
{
    private sealed class <>c
    {
        public static readonly <>c <>9 = new <>c(); // <== HELPER1 CREATION

        public static Func<object, object> <>9__1_0;

        internal object <Test>b__1_0(object k)
        {
            return TestCallback();
        }
    }

    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();

    public object Test(string x)
    {
        if (x == "abort")
        {
            return null;
        }
        return _coll.GetOrAdd(x, <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Func<object, object>(<>c.<>9.<Test>b__1_0)));  // <== HELPER2 CREATION
    }

    public static object TestCallback() //==> STATIC METHOD
    {
        return null;
    }
}

因此,编译器会创建一些帮助程序对象,但只执行一次(帮助程序是静态的)。

现在,如果我从...中删除:staticTestCallback

//ORIGINAL CODE, VERSION 1
public class Program
{
    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
    
    public object Test(String x){
        
        if(x == "abort") return null;
        
        return _coll.GetOrAdd(x, (k)=> TestCallback());
    }
    
    public object TestCallback() => null; //==> INSTANCE METHOD
}

...降低的代码更改为:

//LOWERED CODE, VERSION 2
public class Program
{
    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();

    public object Test(string x)
    {
        if (x == "abort")
        {
            return null;
        }
        return _coll.GetOrAdd(x, new Func<object, object>(<Test>b__1_0)); // <== HELPER1 CREATION
    }

    public object TestCallback()
    {
        return null;
    }

    private object <Test>b__1_0(object k)
    {
        return TestCallback();
    }
}   

现在看来,如果 a 不为 true(即 实际上称为)。new Funcx == "abort"_coll.GetOrAdd

最后,如果我更改为包含一个参数...:Testcallback

//ORIGINAL CODE, VERSION 3
public class Program
{
    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();
    
    public object Test(String x, Func<object> callback){
        
        if(x == "abort") return null;
        
        return _coll.GetOrAdd(x, (k)=> callback());
    }
}

...降低的代码更改为:

//LOWERED CODE, VERSION 3
public class Program
{
    private sealed class <>c__DisplayClass1_0
    {
        public Func<object> callback;

        internal object <Test>b__0(object k)
        {
            return callback();
        }
    }

    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();

    public object Test(string x, Func<object> callback)
    {
        <>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0(); // <== HELPER1 CREATION
        <>c__DisplayClass1_.callback = callback;
        if (x == "abort")
        {
            return null;
        }
        return _coll.GetOrAdd(x, new Func<object, object>(<>c__DisplayClass1_.<Test>b__0)); // <== HELPER2 CREATION
    }
}

在这里,它看起来好像在每次调用时都会创建一个 ,而不管 .new <>c__DisplayClass1_0x == "abort"

总结一下:

  • 版本 1:一次创建 2 个助手。
  • 版本 2:每当实际调用时,都会创建 1 个帮助程序。_cao..GetOrAdd
  • 版本3:在每次调用时创建2个助手。

这是正确的吗?如果降低的代码是正确的(并且是实际编译器使用的代码),为什么在相关调用之前没有立即创建?new <>c__DisplayClass1_0

这样就可以防止不必要的分配。最终,我想知道,这是否是实际的改进:

public IMetadata GetOrDefineMetadata(object key, Func<IMetadata> createCallback)
{
    if (_coll.TryGetValue(key, out var result)) return result; //THIS LINE WAS INSERTED AS AN IMPROVEMENT

    return _coll.GetOrAdd(key, (k) => createCallback()); // ==> WILL THIS STILL CAUSE ALLOCATIONS ON EVERY CALL?
}
C# 闭包 堆内存 分配

评论


答:

1赞 Manuel 10/17/2022 #1

这看起来像是编译器优化的机会。

我把电话转到了_coll。GetOrAdd 到静态方法。在降低的代码中,这会进一步降低分配。

public class Program
{
    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();

    public object Test(String x, Func<object> callback){
        
        if(x == "abort") return null;
    
        return GetOrAdd(x, _coll, callback);
    }

    private static object GetOrAdd(string x, ConcurrentDictionary<object, object> dict,  Func<object> callback)
        {
        return dict.GetOrAdd(x, (_)=> callback());
    }
}

降低版本:

public class Program
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass2_0
    {
        public Func<object> callback;

        internal object <GetOrAdd>b__0(object _)
        {
            return callback();
        }
    }

    private ConcurrentDictionary<object, object> _coll = new ConcurrentDictionary<object, object>();

    public object Test(string x, Func<object> callback)
    {
        if (x == "abort")
        {
            return null;
        }
        return GetOrAdd(x, _coll, callback);
    }

    private static object GetOrAdd(string x, ConcurrentDictionary<object, object> dict, Func<object> callback)
    {
        <>c__DisplayClass2_0 <>c__DisplayClass2_ = new <>c__DisplayClass2_0();
        <>c__DisplayClass2_.callback = callback;
        return dict.GetOrAdd(x, new Func<object, object>(<>c__DisplayClass2_.<GetOrAdd>b__0));
    }
}