【问题标题】:Why aren't closure helper instances (DisplayClass) only created when actually needed?为什么不只在实际需要时才创建闭包助手实例(DisplayClass)?
【发布时间】:2022-10-17 18:25:02
【问题描述】:

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

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

Test 中使用了static 回调函数。而且,根据https://sharplab.io,这得到降低至(缩写):

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

因此,编译器创建了一些辅助对象,但仅这样做一次(助手是静态的)。

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

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

现在看来,new Func 是在每一个调用,如果x == "abort" 不正确(即_coll.GetOrAdd 实际调用)。

最后,如果我将Test 更改为包含callback 参数...:

//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 &lt;&gt;c__DisplayClass1_0每一个打电话,不管x == "abort"

总结一下:

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

这个对吗?如果降低的代码是正确的(并且是实际编译器使用的),为什么new &lt;&gt;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# closures heap-memory allocation


    【解决方案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));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-04-08
      • 1970-01-01
      • 2012-08-28
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-14
      • 1970-01-01
      相关资源
      最近更新 更多