为什么不捕获使用lambda表达式初始化的非捕获表达式树?

萨法克·古尔

考虑以下类别:

class Program
{
    static void Test()
    {
        TestDelegate<string, int>(s => s.Length);

        TestExpressionTree<string, int>(s => s.Length);
    }

    static void TestDelegate<T1, T2>(Func<T1, T2> del) { /*...*/ }

    static void TestExpressionTree<T1, T2>(Expression<Func<T1, T2>> exp) { /*...*/ }
}

这是编译器生成的内容(在少易读的方式):

class Program
{
    static void Test()
    {
        // The delegate call:
        TestDelegate(Cache.Func ?? (Cache.Func = Cache.Instance.FuncImpl));

        // The expression call:
        var paramExp = Expression.Parameter(typeof(string), "s");
        var propExp = Expression.Property(paramExp, "Length");
        var lambdaExp = Expression.Lambda<Func<string, int>>(propExp, paramExp);
        TestExpressionTree(lambdaExp);
    }

    static void TestDelegate<T1, T2>(Func<T1, T2> del) { /*...*/ }

    static void TestExpressionTree<T1, T2>(Expression<Func<T1, T2>> exp) { /*...*/ }

    sealed class Cache
    {
        public static readonly Cache Instance = new Cache();

        public static Func<string, int> Func;

        internal int FuncImpl(string s) => s.Length;
    }
}

这样,通过第一个调用传递的委托将被初始化一次,并在多个Test调用上可重复使用

但是,第二个调用传递的表达式树不会被重用-每个Test调用都会初始化一个新的lambda表达式

如果它不捕获任何东西并且表达式树是不可变的,那么缓存表达式树又会有什么问题呢?

编辑

我想我需要澄清为什么我认为表达式树适合缓存。

  1. 生成的表达式树在编译时就知道了(好吧,它由编译器创建的)。
  2. 它们是不可变的。因此,与下面的X39给出的数组示例不同,表达式树在初始化后无法修改,因此可以安全地进行缓存。
  3. 在代码库中只能有这么多的表达式树-再次,我说的是那些可以缓存的树,即那些使用lambda表达式初始化的树(不是手动创建的树)而不捕获任何外部状态/变量。字符串文字的自动实习将是一个类似的示例。
  4. 它们应被遍历-可以编译它们以创建委托,但这不是它们的主要功能。如果有人想要一个已编译的委托,他们可以只接受一个(a Func<T>,而不是Expression<Func<T>>)。接受表达式树表示它将被用作数据结构。因此,“它们应该首先被编译”不是反对缓存表达式树的明智论据。

我要问的是缓存这些表达式树的潜在缺点。svick提到的内存需求是一个更可能的例子。

埃里克·利珀特

为什么不捕获使用lambda表达式初始化的非捕获表达式树?

我在原始C#3实现和Roslyn重写中都在编译器中编写了该代码。

正如我经常被问到“为什么不”的问题时所说的那样:编译器编写者不需要提供他们做某事的原因做某事需要工作,需要努力并且要花钱。因此,默认位置始终是工作时是不必要做一些事情。

相反,想要完成工作的人需要证明为什么这项工作值得花费。实际上,需求要强于此。想要完成工作的人必须证明为什么不必要的工作比花费开发人员时间的任何其他可能的方法花费时间,精力和金钱更好实际上,有无数种方法可以改善编译器的性能,功能集,健壮性,可用性等。是什么让这个如此出色?

现在,每当我给出这样的解释时,我都会反驳说“微软很富有,等等等等”。拥有大量资源与拥有无限资源并不相同,并且编译器已经非常昂贵。我也遭到推斥,说“开源使劳动力免了”,这绝对不是。

我注意到时间是一个因素。进一步扩展可能会有所帮助。

在开发C#3.0时,Visual Studio有一个特定的日期,即“发布到生产”的日期,这是一个古朴的术语,指的是软件主要分布在CDROM上,一旦打印就无法更改。这个日期不是任意的。相反,随之而来的是整个依赖链。例如,如果SQL Server具有依赖LINQ的功能,则将VS版本推迟到该年的SQL Server版本之后才有意义,因此VS计划会影响SQL Server计划,进而影响其他团队的计划。时间表等等。

因此,VS组织中的每个团队都提交了时间表,而按照该时间表工作最多的团队就是“长杆”。C#团队是VS的长臂,而我是C#编译器团队的长臂,所以每天交付迟到我的编译器功能的每一天都是Visual Studio的一天,每个下游产品都会拖延日程使客户失望

这是进行不必要的性能工作的有力动力,尤其是可能使事情变得更糟而不是更好的性能工作没有到期策略的缓存具有一个名称:这是内存泄漏

如您所述,匿名函数被缓存。当我实现lambda时,我使用了与匿名函数相同的基础结构代码,因此缓存是(1)“降低成本”-工作已经完成,关闭它要比打开它多得多的工作,并且(2)已经由我的前任进行过测试和审查。

我考虑过使用相同的逻辑在表达式树上实现类似的缓存,但是意识到这将(1)可行,需要时间,而我已经很短了;(2)我不知道性能会受到什么影响缓存这样的对象。代表们真的很小代表是一个对象;如果委托在逻辑上是静态的(C#主动缓存的委托是静态的),则它甚至不包含对接收者的引用。相反,表情树可能是巨大的树它们是小对象的图,但该图可能很大。对象图的生存时间越长,垃圾收集器的工作就越多!

因此,由于内存负担是完全不同的,因此无论使用什么性能测试和度量标准来证明决定是否要缓存代表,都不适用于表达式树。我不想在我们最重要的新语言功能中创建新的内存泄漏源。风险太大。

但是,如果收益很大,风险可能值得。那有什么好处呢?首先问问自己“表达式树在哪里使用?” 在LINQ中,将要查询到数据库的查询。这在时间和内存上都是非常昂贵的操作添加缓存并不能使您大获全胜,因为您要做的工作比获胜要贵上百万倍。胜利就是噪音。

与此相比,代表们赢得了表演。“分配x => x + 1,然后调用它”一百万次与“检查缓存,如果未缓存则分配它,调用它”之间的区别是为检查分配一个分配,这可以节省整个纳秒。这似乎没什么大不了,但是呼叫也将花费纳秒,因此按百分比计算,这非常重要。缓存代表是一个明显的胜利。缓存表达式树远没有一个明显的胜利。我们需要的数据是证明风险的好处。

因此,不花时间在C#3中这种不必要的,可能不明显的,不重要的优化上是很容易的决定。

在C#4中,除了重新考虑此决定外,我们还有许多重要的事情要做。

在C#4之后,该团队分为两个子团队,一个子团队重写编译器“ Roslyn”,另一个子团队在原始编译器代码库中实现async-await。通过实现该复杂而困难的功能,异步等待团队被完全消耗掉了,当然,该团队比平时要小。他们知道自己的所有工作最终都将在罗斯林被复制,然后被丢弃。那个编译器已经寿终正寝了。因此,没有动力花费时间或精力来添加优化。

当我在Roslyn中重写代码时,拟议的优化是我要考虑的事情,但是我们的最高优先级是使编译器端到端地工作,然后再对它的一小部分进行优化。在那之前,我于2012年离开了Microsoft工作完成了。

至于为什么我离开后我的同事都没有再讨论这个问题,您必须要问他们,但是我敢肯定,他们非常忙于根据实际客户要求的实际功能或性能优化进行实际工作。以更少的成本获得更大的胜利。这项工作包括开源编译器,这并不便宜。

因此,如果您想完成这项工作,则可以选择。

  • 编译器是开源的。你可以自己做。如果这听起来像是一件繁琐的工作,但对您却几乎没有什么好处,那么您现在可以更直观地了解自2005年实施该功能以来为何没人进行这项工作。

当然,这对于编译器团队来说仍然不是“免费”的。有人将需要花费时间,精力和金钱来审查您的工作。请记住,性能优化的大部分成本不是更改代码所需的五分钟时间。经过数周的测试,在所有可能的实际条件下进行的测试表明,该优化有效且不会使情况更糟!表演工作是我做的最昂贵的工作。

  • 设计过程是开放的。输入一个问题,并在该问题中给出一个令人信服的理由,为什么您认为此增强功能值得。随着数据。

到目前为止,您只说了为什么可行可能不会削减!许多事情都是可能的。给我们提供数字,证明为什么编译器开发人员应该花时间进行增强,而不是实现客户要求的新功能。

避免复杂表达式树重复分配的实际好处是避免了收集压力,这是一个严重的问题。C#中的许多功能都是为了避免收集压力而设计的,而表达式树并不是其中之一。如果要进行此优化,我对您的建议是专注于其对压力的影响,因为这是您找到最大的胜利并能够提出最有说服力的论据的地方。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章