应该避免LINQ,因为它很慢?
我已经被告知,因为.net linq是如此之慢,我们不应该使用它,并想知道其他人已经得出了相同的结论,例如:
花了1443ms做了10亿比较非LINQ。
拿了4944ms做了10亿比较LINQ。
(减慢243%)
非LINQ代码:
for (int i = 0; i < 10000; i++) { foreach (MyLinqTestClass1 item in lst1) //100000 items in the list { if (item.Name == "9999") { isInGroup = true; break; } } }
花了1443ms做了10亿比较非LINQ。
LINQ代码:
for (int i = 0; i < 10000; i++) isInGroup = lst1.Cast<MyLinqTestClass1>().Any(item => item.Name == "9999");
拿了4944ms做了10亿比较LINQ。
我猜是可能的,以优化LINQ代码,但认为它很容易得到真正慢的LINQ代码,并认为它不应该被使用。 鉴于LINQ是慢的,那么也将遵循,PLINQ是慢的,NHibernate的LINQ将是缓慢的,所以不应该使用任何types的LINQ语句。
有没有人发现,LINQ是慢的,希望他们从来没有使用过,或者是我们会基于这样的基准进行过于普遍的脑震荡?
Linq应该避免,因为它的速度慢吗?
不,如果速度不够快 ,应该避免。 慢 , 不够快,不完全是一回事!
慢与您的客户,pipe理层和利益相关者无关。 速度不够是非常相关的。 永远不要测量东西有多快 ; 它告诉你没有什么可以用来做出商业决策的。 衡量客户接受程度有多接近 。 如果可以接受的话,就不要花钱把钱花得更快。 这已经够好了。
性能优化是昂贵的 。 编写代码以使其可以被其他人阅读和维护是很昂贵的 。 这些目标经常是彼此对立的,所以为了负责任地利用利益相关者的钱,你必须确保你只花费宝贵的时间和精力来对不够快的事情进行性能优化。
你已经发现了一个人为的,不切实际的基准情况,LINQ代码比编写代码的其他方式要慢。 我向你保证,你的客户不关心你不切实际的基准的速度。 他们只关心你发给他们的程序对他们来说太慢了。 我向你保证,你的pipe理层不在乎那个(如果他们是胜任的)。 他们关心的是,你花费了多less钱,无谓地花费了更快的速度,使代码在阅读,理解和维护过程中更加昂贵。
你为什么使用Cast<T>()
? 基本上,你没有给我们足够的代码来真正判断基准。
是的,你可以使用LINQ来写慢代码。 你猜怎么了? 你也可以编写慢的非LINQ代码。
LINQ 极大地提高了代码处理数据的performance力……而且编写性能不错的代码并不难,只要你花时间理解LINQ就可以了。
如果有人告诉我不要使用LINQ(尤其是LINQ to Objects),那么我就会笑到他们的脸上。 如果他们提出了一个特定的瓶颈,并说:“在这种情况下,我们可以通过不使用LINQ来加快速度,这里有证据”,那么这是一个非常不同的问题。
也许我已经错过了一些东西,但是我很确定你的基准testing已经结束了。
我用以下方法testing:
-
Any
扩展方法(“LINQ”) - 一个简单的
foreach
循环(你的“优化”方法) - 使用
ICollection.Contains
方法 -
Any
使用优化数据结构的扩展方法(HashSet<T>
)
这里是testing代码:
class Program { static void Main(string[] args) { var names = Enumerable.Range(1, 10000).Select(i => i.ToString()).ToList(); var namesHash = new HashSet<string>(names); string testName = "9999"; for (int i = 0; i < 10; i++) { Profiler.ReportRunningTimes(new Dictionary<string, Action>() { { "Enumerable.Any", () => ExecuteContains(names, testName, ContainsAny) }, { "ICollection.Contains", () => ExecuteContains(names, testName, ContainsCollection) }, { "Foreach Loop", () => ExecuteContains(names, testName, ContainsLoop) }, { "HashSet", () => ExecuteContains(namesHash, testName, ContainsCollection) } }, (s, ts) => Console.WriteLine("{0, 20}: {1}", s, ts), 10000); Console.WriteLine(); } Console.ReadLine(); } static bool ContainsAny(ICollection<string> names, string name) { return names.Any(s => s == name); } static bool ContainsCollection(ICollection<string> names, string name) { return names.Contains(name); } static bool ContainsLoop(ICollection<string> names, string name) { foreach (var currentName in names) { if (currentName == name) return true; } return false; } static void ExecuteContains(ICollection<string> names, string name, Func<ICollection<string>, string, bool> containsFunc) { if (containsFunc(names, name)) Trace.WriteLine("Found element in list."); } }
不要担心Profiler
类的内部。 它只是在一个循环中运行Action
,并使用Stopwatch
来计时。 它还确保在每次testing之前调用GC.Collect()
以尽可能多地消除噪音。
结果如下:
Enumerable.Any: 00:00:03.4228475 ICollection.Contains: 00:00:01.5884240 Foreach Loop: 00:00:03.0360391 HashSet: 00:00:00.0016518 Enumerable.Any: 00:00:03.4037930 ICollection.Contains: 00:00:01.5918984 Foreach Loop: 00:00:03.0306881 HashSet: 00:00:00.0010133 Enumerable.Any: 00:00:03.4148203 ICollection.Contains: 00:00:01.5855388 Foreach Loop: 00:00:03.0279685 HashSet: 00:00:00.0010481 Enumerable.Any: 00:00:03.4101247 ICollection.Contains: 00:00:01.5842384 Foreach Loop: 00:00:03.0234608 HashSet: 00:00:00.0010258 Enumerable.Any: 00:00:03.4018359 ICollection.Contains: 00:00:01.5902487 Foreach Loop: 00:00:03.0312421 HashSet: 00:00:00.0010222
数据非常一致,并讲述了以下故事:
-
使用
Any
扩展方法比使用foreach
循环慢9%左右。 -
使用最适当的方法(
ICollection<string>.Contains
)与未优化的数据结构(List<string>
)比使用foreach
循环快大约50%。 -
在性能方面,使用优化的数据结构(
HashSet<string>
)完全可以将任何其他方法排除在外。
我不知道你从哪里得到243%。 我的猜测是它与所有的投射有关。 如果您使用的是ArrayList
那么您不仅使用未优化的数据结构,而且还使用了大部分已经过时的数据结构。
我可以预测接下来会发生什么。 “是的,我知道你可以对它进行更好的优化,但是这只是一个比较LINQ和非LINQ性能的例子。
是的,但如果你不能在你的例子中彻底,你怎么可能期望在生产代码这是彻底的?
底线是这个:
如何构build和devise您的软件比使用什么特定的工具和时间更为重要。
如果遇到性能瓶颈 – 这与LINQ vs.无可能发生的情况相同,那就解决它们。 埃里克对自动化性能testing的build议是一个很好的build议; 这将帮助您尽早发现问题,以便您可以正确地解决问题 – 而不是回避一个让您的工作效率提高80%,但是却会导致性能降低10%的惊人工具,但是通过实际调查问题并提出问题有一个真正的解决scheme ,可以提高您的性能2或10或100或更多。
创build高性能应用程序不是关于使用正确的库。 这是关于分析,做出好的deviseselect,并编写好的代码。
LINQ是一个真实世界的瓶颈(影响应用程序的总体性能还是感知性能)?
您的应用程序是否将在现实世界中的1,000,000,000多条logging上执行此操作? 如果是这样的话 – 那么你可能要考虑替代品 – 如果不是这样的话,就好像说“我们不能买这种家用轿车,因为它在180多公里的时候运转不好”。
如果它只是“慢”,那么这不是一个很好的理由……通过这个推理,你应该把所有的东西写在asm / C / C ++中,而C#应该由于“太慢”而离开桌子。
虽然过早的pessimization(imho)与过早的优化一样糟糕,但是不应该将使用环境考虑在内,不应该排除基于绝对速度的整个技术。 是的,如果你正在做一些非常繁重的数字处理,这是一个瓶颈 ,那么LINQ 可能会有问题 – 描述它。
你可以用LINQ的一个论点是,虽然你可能用手写代码胜过它,但是LINQ版本可能会更加清晰和容易维护 – 而且,与复杂的手动并行相比,PLINQ还有其他优势。
这种比较的问题是,它的抽象是毫无意义的。
如果一个人的Name属性对MyLinqTestClass1对象进行哈希操作,可以击败其中的任何一个。 在这两者之间,如果可以按名称对它们进行sorting,然后进行二分查找。 事实上,我们不需要存储MyLinqTestClass1对象,我们只需要存储名称。
内存大小有问题? 也许在DAWG结构中存储名称,结合足够的,然后用这个检查?
设置这些数据结构的额外开销是否有意义? 这是不可能的。
另外一个问题是LINQ这个概念的另一个问题,就是它的名字。 对于营销目的来说,MS能够说“这里有一堆很酷的新东西可以一起工作”,但是当人们把他们分开的时候把他们合并在一起。 你必须调用Any
,基本上实现了.NET 2.0中常见的可枚举filter模式(对于.NET1.1来说并不是未知的,尽pipe它的写法更加尴尬,这意味着它只能在效率方面受益在某些情况下真的很重要),你有lambdaexpression式,并且你有一个查询树都在一个概念中凝聚在一起。 哪一个是慢的?
我敢打赌,这里的答案是lambda,而不是Any
的使用,但我不会赌大量(例如项目的成功),我会testing和确定。 同时,lambdaexpression式与IQueryable一起工作的方式可以使特别有效的代码变得非常有效,即使不使用lambdaexpression式,编写相当的效率也是非常困难的。
当LINQ的效率很高时,我们是不是没有效率? 我不这么认为。
使用LINQ的地方是有道理的。
在瓶颈条件下,尽pipe它看起来适当或不适当的优化, 然后离开或LINQ。 不要先写难以理解的代码,因为您只会更加难以实现真正的优化。
对我来说,这听起来像是你正在签订合同,而雇主要么不懂LINQ,要么不了解系统的性能瓶颈。 如果您正在使用GUI编写应用程序,那么使用LINQ的性能影响可以忽略不计。 在典型的GUI / Web应用程序中,内存调用占所有等待时间的不到1%。 你,或者说你的雇主,正试图优化1%。 这真的有益吗?
但是,如果您正在编写一个科学性或重度math导向的应用程序,只需很less的磁盘或数据库访问权限,那么我同意LINQ不是。
顺便说一句,演员是不需要的。 以下function与您的第一个testing等效:
for (int i = 0; i < 10000; i++) isInGroup = lst1.Any(item => item.Name == "9999");
当我运行这个使用包含10,000个MyLinqTestClass1对象的testing列表时,原来的2.79秒运行,修正了3.43秒。 对于可能占用CPU时间不到1%的操作,节省30%并不能很好地利用您的时间。
也许linq很慢,但与linq我可以并行我的代码非常简单。
喜欢这个:
lst1.Cast<MyLinqTestClass1>().AsParallel().Any(item => item.Name == "9999");
你将如何平行化循环?
这里有一个有趣的观察,因为你提到nHibernate由于LINQ速度缓慢而变慢。 如果你正在做LINQ to SQL(或者nHibernate等效),那么你的LINQ代码转换成SQL服务器上的EXISTS查询,因为你的循环代码必须首先获取所有行,然后遍历它们。 现在,您可以轻松编写这样的testing,以便循环代码为所有10K运行读取所有数据(单个数据库查找),但LINQ代码实际上执行10K SQL查询。 这可能会显示在现实中不存在的循环版本的速度优势。 实际上,一个EXISTS查询每次都会优于表扫描和循环 – 即使您在查询的列上没有索引(如果经常执行此查询,您可能也会这样做)。
我并不是说你的testing就是这种情况 – 我们没有足够的代码去看 – 但可能是这样。 这也可能是真的有一个与LINQ到对象的性能差异,但是可能不会转换为LINQ to SQL。 你需要知道你正在测量的是什么,以及它是如何适用于你的真实世界的需求。
“我已经被告知(由谁?),因为.net linq是如此缓慢[什么?],我们不应该使用它”
根据我的经验,根据某个人 曾经告诉过你的是什么技术,图书馆或者语言来决定是不是一个好主意。
首先,这些信息是否来自您所信任的来源? 如果不是的话,那么你可能会犯下一个相信这个(也许是未知的)人做出devise决定的巨大错误。 其次,这个信息今天还是相关的吗? 但是没关系,基于你简单而不是很现实的基准,你已经得出结论,LINQ比手动执行相同的操作要慢。 自问的问题是:这个代码性能是否关键? 这个代码的性能是否会受到LINQ查询执行速度以外的其他因素的限制 – 考虑数据库查询,等待I / O等?
以下是我喜欢的工作方式:
- 确定要解决的问题,并根据您已知的要求和限制编写最简单的function完整的解决scheme
- 确定你的实现是否真的满足要求(是否足够快?资源消耗是否保持在可接受的水平?)。
- 如果是这样,你就完成了。 如果没有,find方法来优化和改进你的解决scheme,直到它通过#2的testing。 这是你可能需要考虑放弃的地方,因为它太慢了。 也许。 然而,有可能的是,瓶颈并不在你期望的地方。
对我来说,这个简单的方法只有一个目的:通过最小化我花在改进已经足够好的代码上的时间来最大化我的工作效率。
是的,当你发现原来的解决scheme不能再削减的时候,这一天就可能到来。 或者它可能不会。 如果是这样,那么就在那里处理。 我build议你避免浪费你的时间来解决假设的(未来的)问题。
你是对的。 在LINQ中编写慢代码很容易。 其他人也是对的:在没有LINQ的C#中编写慢代码很容易。
我在C中编写了和C一样的循环,并且运行了几毫秒。 我从中得出的结论是C#本身很慢。
和你的LINQ->循环扩展一样,在C语言中,执行相同的操作需要多于5倍的代码行数,这使得编写起来更慢,更难以阅读,更容易出错,更难以find修复它们,但是如果每十亿次迭代节省几毫秒是很重要的,那通常就是这样。
正如您演示的那样,可以编写比LINQ代码更好的非LINQ代码。 但是反过来也是可以的。 考虑到LINQ可以提供的维护优势,您可能会考虑对LINQ进行违约,因为不太可能遇到任何可归因于LINQ的性能瓶颈。
这就是说,有些情况下LINQ只是不行。 例如,如果要导入大量数据,则可能会发现执行单个插入的操作比以批量的XML将数据发送到SQL Server要慢。 在这个例子中,并不是LINQ插入比非LINQ插入要快,相反,它并不是倾向于为批量数据导入执行单独的SQL插入。
我宁愿说,你应该避免努力写出最有效率的代码,除非它是强制性的。
鉴于LINQ是慢的,那么也将遵循,PLINQ是慢的,NHibernate的LINQ将是缓慢的,所以不应该使用任何types的LINQ语句。
这是一个不同的背景下,但是有着惊人的不同。 当谈到数据访问操作时,整个10亿次操作的1.4秒和5秒是无关紧要的。
你的testing案例有点歪斜。 任何操作者将开始枚举你的结果,如果发现并退出,则返回true。 尝试使用简单的string列表来查看结果。 要回答你关于避免LINQ的问题,你应该转向使用LINQ。 除了编译时间检查之外,它还使代码更容易阅读。 你也不需要在你的例子中使用Cast操作符。
string compareMe = "Success"; string notEqual = "Not Success"; List<string> headOfList = new List<string>(); List<string> midOfList = new List<string>(); List<string> endOfList = new List<string>(); //Create a list of 999,999 items List<string> masterList = new List<string>(); masterList.AddRange(Enumerable.Repeat(notEqual, 999999)); //put the true case at the head of the list headOfList.Add(compareMe); headOfList.AddRange(masterList); //insert the true case in the middle of the list midOfList.AddRange(masterList); midOfList.Insert(masterList.Count/2, compareMe); //insert the true case at the tail of the list endOfList.AddRange(masterList); endOfList.Add(compareMe); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); headOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Reset(); stopWatch.Start(); midOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Reset(); stopWatch.Start(); endOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Stop();
The type casting is of course going to slow your code down. If you care that much, at least used a strongly typed IEnumerable for the comparison. I myself try to use LINQ wherever possible. It makes your code much more concise. It's not not to have to worry about the imperative details of your code. LINQ is a functional concept, which means you'll spell out what you want to happen and not worry about how.
There are a thousand times better reasons to avoid Linq.
Following quote from a discussion on Linq names a few of them:
QUOTE1
"For instance this works:
var a = new { x = 1, y = 2 }; a = new { x = 1, y = 3 };
But this does not work:
var a = new { x = 1, y = 2 }; a = new { x = 1, y = 2147483649 };
It returns : Error 1 Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'
但是这个工作:
var a = new { x = 1, y = 2147483648 }; a = new { x = 1, y = 2147483649 };
When you compile:
var a = new { x = 1, y = 2 };
The type of the x and y components is arbitrarily declared as a 32 bit signed integer, and it is one of the many integer types the platform has, without anything special.
But there is more. For instance this works:
double x = 1.0; x = 1;
But this does not work:
var a = new { x = 1.0, y = 0 }; a = new { x = 1, y = 0 };
The numeric conversion rules are not applicable to this kind of types. As you can see, elegance is in every detail."
QUOTE2
"It appears, then, that 'AnonymousType#1' and 'AnonymousType#2' are not synonymous–they name distinct types. And as { x = 1, y = 2 }
and { y = 2, x = 1 }
are expressions of those two types, respectively, not only do they denote distinct values, but also values of distinct types.
So, I was right to be paranoid. Now my paranoia extends even further and I have to ask what LinQ makes of the following comparison:
new { x = 1, y = 2 } == new { x = 1, y = 2 }
The result is false because this is a pointer comparison.
But the result of:
(new { x = 1, y = 2 }).Equals(new { x = 1, y = 2 })
Is true.
And the result of:
(new { x = 1, y = 2 }).Equals(new { y = 2, x = 1 })
和
(new { x = 1, y = 2 }).Equals(new { a = 1, b = 2 })
Is false."
QUOTE3
"updates are record oriented :-O
This, I agree, is problematic, and derives from LINQ's sequence-oriented nature.
This is a show stopper for me. If I have to use SQL for my updates anyway why to bother about LinQ?
the optimization in LinQ to objects is unexistent.
There is not any algebraic optimization nor automatic expression rewrite. Many people don't want to use LinQ to Objects because they lose a lot of performance. Queries are executed in the same way as you write them."