LINQ 在简单操作中的效率
本文关键字:效率 操作 简单 LINQ | 更新日期: 2023-09-27 18:34:35
我最近发现了LINQ,我发现它使用起来非常有趣。目前我有以下功能,我不确定它是否会更有效率,毕竟会产生相同的输出。
你能告诉我你对此的看法吗?
该函数只是以一种非常简单的方式删除标点符号:
private static byte[] FilterText(byte[] arr)
{
List<byte> filteredBytes = new List<byte>();
int j = 0; //index for filteredArray
for (int i = 0; i < arr.Length; i++)
{
if ((arr[i] >= 65 && arr[i] <= 90) || (arr[i] >= 97 && arr[i] <= 122) || arr[i] == 10 || arr[i] == 13 || arr[i] == 32)
{
filteredBytes.Insert(j, arr[i]) ;
j++;
}
}
//return the filtered content of the buffer
return filteredBytes.ToArray();
}
LINQ 替代方案:
private static byte [] FilterText2(byte[] arr)
{
var x = from a in arr
where ((a >= 65 && a <= 90) || (a >= 97 && a <= 122) || a == 10 || a == 13 || a == 32)
select a;
return x.ToArray();
}
比简单循环和过程代码的效率略低,但差异通常很小,并且简洁易读通常值得将简单投影和筛选转换为 LINQ。
如果性能确实很重要,请自行衡量并自行确定 LINQ 代码的性能是否足够。
LinQ 非常适合保持简单。在性能方面,如果您开始对列表、数组等进行大量转换,它确实会成为一个问题。
MyObject.where(...).ToList().something().ToList().somethingelse.ToList();
众所周知,这是一个杀手锏,尽量晚点转换为最终名单。
螺丝性能,LINQ 很糟糕,因为:
private static bool IsAccepted(byte b)
{
return (65 <= b && b <= 90) ||
(97 <= b && b <= 122) ||
b == 10 || b == 13 || b == 32;
}
arr.Where(IsAccepted).ToArray(); // equivalent to FilterText(arr)
也就是说,你不写如何写,而只是写什么。此外,它与您介绍的另一种方法一样快(慢(:Where(..)
在内部创建一个列表并将其转换为数组 iirc 的 ToArray()
中被懒惰地评估。
顺便说一下,字符串在 C# 中是 Unicode,所以不要用它来做一些简单的字符串格式化(有更好的替代方案(。
在大多数情况下,我同意@MarkByers。 Linq 的效率会比过程代码低一些。 通常,缺陷可以追溯到表达式树的编译。尽管如此,在99%的情况下,可读性和时间的改进是值得打击的。 遇到性能问题时,请进行基准测试、修改和重新基准测试。
话虽如此,LINQ 与 lambda 和匿名委托密切相关。 这些功能经常被谈论,就好像它们是一回事一样。 在某些情况下,这些构造可能比过程代码更快。 看起来您的示例可能是其中一种情况。 我会按如下方式重写您的代码:
private static byte [] FilterText2(byte[] arr) {
return arr.Where( a=> (a >= 65 && a <= 90) ||
(a >= 97 && a <= 122) ||
a == 10 || a == 13 || a == 32
).ToArray();
}
同样,为您的特定方案做一些基准标记,如 YMMV。 大量的墨水洒在哪个更快,在什么情况下。 以下是其中的一些墨水:
- http://blog.jerrynixon.com/2010/02/revisiting-c-loop-performance.html
- http://blog.thijssen.ch/2009/02/linq-vs-lambda-vs-loop-performance-test.html
- 在 .NET 中,哪个循环运行得更快,"for"还是"foreach"?
- 何时不使用 lambda 表达式
许多 LINQ 语句很容易并行化。 只需在查询的开头添加AsParallel()
即可。 如果您希望以牺牲某些性能为代价来保留原始订单,也可以添加AsOrdered()
。 例如,以下 LINQ 语句:
arr.Where(IsAccepted).ToArray();
可以写成:
arr.AsParallel().AsOrdered().Where(IsAccepted).ToArray();
您只需要确保其开销不会超过其收益:
var queryA = from num in numberList.AsParallel()
select ExpensiveFunction(num); //good for PLINQ
var queryB = from num in numberList.AsParallel()
where num % 2 > 0
select num; //not as good for PLINQ
每个好的编写的命令式代码都会比好的书面声明式代码更有效的时间和空间,因为声明式代码必须转换为命令式代码(除非你拥有一台 Prolog 机器......你可能没有,因为你问的是.Net :-((。
但是,如果能够以比使用循环更简单、更易读的方式使用 LINQ 解决问题,那么这是值得的。当您看到类似内容时
var actualPrices = allPrices
.Where(price => price.ValidFrom <= today && price.ValidTo >= today)
.Select(price => price.PriceInUSD)
.ToList();
这是"一条线",第一眼就很明显它在做什么。声明一个新集合,循环访问旧集合,编写if并向新集合添加一些内容不是。因此,如果您不想节省每一毫秒(您可能不想,因为您使用的是 .Net 而不是带有嵌入式 ASM 的 C(,那么这是一个胜利。而且 LINQ 是高度优化的 - 有更多的代码库 - 一个用于集合,一个用于 XML,一个用于 SQL......,所以它通常不会慢多少。没有理由不使用它。
一些 LINQ 表达式可以使用并行 LINQ 轻松并行化,几乎"免费"(= 没有更多的代码,但并行性开销仍然存在,所以算上它(。