.net LINQ是否足够聪明,不会多次检查条件标志?

pexxcrt2  于 2023-02-10  发布在  .NET
关注(0)|答案(3)|浏览(109)

我的问题是,当numbers具体化numbers集合时,下面代码中的LINQ会读取flag值三次吗?我正在尝试优化我的代码。这里我希望Where子句只计算一次,如果flag == true

List<int> list = new(){1, 2, 3};

bool flag = true;

bool IsNumberBig(int num)
{
    return num > 100;
}

var numbers = list.Where(l => flag || IsNumberBig(l)).ToList();

我没能找到一个相关的问题。将感谢看看我如何可以检查这个自己。

mzillmmw

mzillmmw1#

flag的值将在每次调用lambda时被求值,显然,这比求值IsNumberBig()(或其他更复杂的方法)要便宜,但仍然不是免费的。
要优化此功能,可以编写如下代码

List<int> numbers;
if (flag)
{
    numbers = list;
}
else
{
    numbers = list.Where(IsNumberBig).ToList();
}

与此类似,如果flag为true,则不进行迭代(在您的情况下,无论如何,将返回所有元素)

kkih6yb8

kkih6yb82#

我认为需要注意的是LINQ主要是语法糖。它不做优化。绝大多数优化是由编译器完成的,或者更具体地说,是抖动。
讨论优化时的一个问题是,只要结果相同,抖动就可以执行任何类型的优化。但它也必须尽可能快地执行任何优化,所以它很少做所有允许它做的事情。它还取决于编译器的版本,最近的版本具有分层编译功能,以便更好地优化频繁使用的循环。
由于所有这些原因,很难猜测编译器会优化什么,不会优化什么,最好的方法是只对代码进行基准测试。使用Benchmark.Net进行或不进行检查,这样你都会得到正确的答案。它还应该告诉你是否需要担心性能差异。
即使你很难猜到编译器会做什么,但还是有一些事情值得注意。大多数优化都是在一个方法中完成的,编译器不会试图重写方法签名。然而,小方法往往是 * 内联的 *,因此可以作为调用方法的一部分进行优化。因此,如果所有代码都是内联的,那么很可能会删除flag检查。然而,阻止内联的因素之一是间接调用,例如通过接口调用方法,或者在本例中调用 delegate。2 LINQ中几乎所有的东西都是委托和接口,这往往会妨碍性能。3所以一般来说,使用LINQ是为了方便,而不是性能。
尽管如此,现代处理器拥有相当惊人的分支预测器,所以我认为这种容易预测的分支的影响相当小,可能还有其他因素对性能有更大的影响。
但最重要的是对代码进行基准测试和/或分析,而不是仅仅猜测性能。对于试图优化完全错误的东西的人来说,这是很常见的,即使是有经验的开发人员。如果你想开始,请查看Measure app performance in visual studioBenchmark .net

sxpgvts3

sxpgvts33#

您可以执行以下扩展:
它基本上将@PMF的答案封装在一个扩展方法中,所以你可以像使用你已经知道的Where一样使用它。它只需要一个额外的condition参数,用于打开/关闭predicate的应用程序。这样做的好处是你可以像使用普通的Where一样将它与其他Linq方法链接起来。

using System.Linq;
using System.Collections.Generic;

public static class LinqEx
{
    public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, bool condition, Func<T, bool> predicate)
    {
        return condition 
           ? source.Where(predicate)
           : source;
    }
}

然后像这样使用它:

var someList = // assume it is a List<int> with some items
var shallFilter = true; // or false
var filteredList = someList.WhereIf(shallFilter, l => IsBigNumber(l)).ToList();

在此Fiddle中查看实际应用。
请注意,这在与数据库相关(Linq to SQL)的设置中会更有意义,因为这实际上是一个微观优化。要显示效果,您必须有许多项和一个非常昂贵的 predicate 。
你的密码里也有一个词:

var numbers = list.Where(l => flag || IsNumberBig(l)).ToList();

如果flag为真,则flag || X将计算为真,而不考虑X。X甚至不会被计算。
因此,您基本上实现了与您的需求相反的内容。
另请参阅:条件逻辑OR运算符||

相关问题