介绍
自从微软在 .NET 3.5 中引入LINQ已经 15 年(2007 年) 。这应该有足够的时间让它成熟为一个全方位的有用工具。
LINQ的使用有几个方面:
- 延迟执行和延迟评估(更好地利用多线程系统)
- 缩短代码(可读性和可重用性)
- 所用技术的运行时开销(性能)
在本文中,我将只了解 LINQ 的一个方面——性能。
背景
LINQ - 至少在我的环境中 - 以其外观使所有 C# 程序员兴奋不已,随后导致该技术的大量使用。
我想在这里问一下,如果一个人完全清醒地看待 LINQ:作为扩展方法和匿名委托(lambda 表达式)与延迟执行(yield)的创新组合,还有多少营销亮点?
为了从性能方面回答这个问题,我想用 LINQ 过滤一个对象列表并对结果进行排序。
这种方法的特殊性是 LINQ 必须处理列表直到最后一个元素。因此,我有意消除Any()
使用or可以实现的运行时优势,First()
并专注于必须处理所有元素的情况。
测试
当然,结果取决于底层运行时系统、操作系统和硬件的多线程能力——因此,仅将在同一环境中运行的不同方法相互比较是有用的。为此,我准备了 4 个可以相互比较的测试用例。
- 测试用例 1:使用 C++ 的 LINQ(无排序和有排序)
- 测试用例 2 :使用 C++ 的经典(
for(...)
循环if(...)
和)(无排序和有排序)sort(...)
- 测试用例 3:使用 C# 的 LINQ(无排序和有排序)
- 测试用例 4:带有 C# 的经典(
for(...)
循环if(...)
和sort(...)
)(没有和有排序)
并且测试用例将在两个不同的环境中执行......
在Win10中测试
第一个测试环境是配备 i7-5600U @ 2.6GHz、Windows 10 x64、.NET 4.7.2 和 16GB 内存的 Lenovo T550。
C++ 测试结果如下:
C#测试结果如下:
在 openSUSE 上测试
第二个测试环境是联想 T520,配备 i5-2540M @ 2.6GHz、openSUSE Leap 15.4 x64、.NET 6.0 和 12 GB 内存。
C++ 测试结果如下:
C#测试结果如下:
结果比较
为了更好地比较结果,我忽略了最差的测试运行(最长的运行时间)并取其余三个测试运行的中值。这是这张摘要图片:
线 | 系统 | 硬件 | 语 | 试图 | 订购 | 中位数 | C++ 与 C# | LINQ 与经典 |
1 | 赢得 10 | T550 | C++ | LINQ | 不 | 23.1 毫秒 | 77 %worse | 差 2.5 倍 |
2 | 赢得 10 | T550 | C# | LINQ | 不 | 13.0 毫秒 | 好 77 % | |
3 | 赢得 10 | T550 | C++ | 经典的 | 不 | 9.2 毫秒 | 73 %worse | 约好 2.5 倍 |
4 | 赢得 10 | T550 | C# | 经典的 | 不 | 5.3 毫秒 | 好 73 % | |
5 | 赢得 10 | T550 | C++ | LINQ | 是的 | 19.0 毫秒 | 好 186 % | 不一致的 |
6 | 赢得 10 | T550 | C# | LINQ | 是的 | 54.3 毫秒 | 差 186 % | |
7 | 赢得 10 | T550 | C++ | 经典的 | 是的 | 7.6 毫秒 | 1309 % 更好 | 不一致的 |
8 | 赢得 10 | T550 | C# | 经典的 | 是的 | 107.1 毫秒 | 差 1309 % | |
9 | openSUSE | T520 | C++ | LINQ | 不 | 11.5 毫秒 | 好 60 % | ~ 3 倍差 |
10 | openSUSE | T520 | C# | LINQ | 不 | 18.4 毫秒 | 差 60% | |
11 | openSUSE | T520 | C++ | 经典的 | 不 | 5.1 毫秒 | 平等的 | 〜3倍好 |
12 | openSUSE | T520 | C# | 经典的 | 不 | 5.1 毫秒 | 平等的 | |
13 | openSUSE | T520 | C++ | LINQ | 是的 | 10.5 毫秒 | 好 395 % | 不一致的 |
14 | openSUSE | T520 | C# | LINQ | 是的 | 52.0 毫秒 | 差 395 % | |
15 | openSUSE | T520 | C++ | 经典的 | 是的 | 5.1 毫秒 | 23169 % 更好 | 不一致的 |
16 | openSUSE | T520 | C# | 经典的 | 是的 | 123.4 毫秒 | 差 23169 % |
预期和结果的讨论
C++ 与 C#
- 我期望 C++ 比 C# 有明显的优势。
- 期望达到了。但是,并没有预期的那么一致。
除了第 1/2 行和第 3/4 行的突破之外,C++ 比 C# 更快——但并不显着。但是,只有通过排序,差异才会变得非常明显。显然,STL 和 C++ 编译器优化器在这里做得很好。这在第 7/8 行和第 15/16 行变得非常清楚——以一种几乎令人难以置信的方式。但是我已经重复了几次测试运行 - 结果没有显着变化。C# 总是失败。
LINQ 与经典(for(...) 循环、 if(...) 和 sort(...))
- 我希望 LINQ 与经典方法相比有明显的劣势。
- 期望达到了。然而,并没有想象中那么清晰。
对于 C++ 和 C# , LINQ 总是比经典方法(for(...)
循环if(...)
和)更糟糕。sort(...)
但差异小于预期。
我认为 LINQ 在 C++ 中表现如此出色的原因是它是一个模板实现,可以转换为高度优化的机器代码。
我认为 LINQ 在 C# 中表现如此出色的原因是特殊的数据结构。C++ 中的 LINQvector<Element>
用于源数据和结果,而 C# 中的 LINQList<Element>
用于源数据和System.Linq.Enumerable.WhereListIterator<Element>
/或System.Linq.OrderedEnumerable<Element, int>
结果。第二个datatype
提供了对排序标准的明确访问,也可以被视为不再具有可比性或作弊。但让我们温和一点。
Wind10 与 openSUSE
我不想进一步评论它,但测试似乎在 Linux 下在较弱的硬件上运行得更快。也许是因为 Microsoft Defender - 它总是在我的 Windows 机器上运行。
缩放
与 C++ 相比,C# 相当可接受的结果让我感到惊讶(我曾预计至少有 10 次方作为差异),也让我感到高兴(显然 15 年的发展确实对 C# 产生了积极影响)。
不过,我想确定 C# 真的那么强大。640000
所以我简单地将数据量从到乘以 20 倍12800000
。
缩放后的 C++ 测试结果为:
缩放后的 C# 测试结果为:
这是这张摘要图片:
线 | 系统 | 硬件 | 语 | 试图 | 订购 | 中位数 | C++ 与 C# | LINQ 与经典 |
1 | openSUSE | T520 | C++ | LINQ | 不 | 258.9 毫秒 | 30 %worse | ~ 差 2 倍 |
2 | openSUSE | T520 | C# | LINQ | 不 | 199.8 毫秒 | 好 30% | |
3 | openSUSE | T520 | C++ | 经典的 | 不 | 135.4 毫秒 | 21 %worse | ~ 好 2 倍 |
4 | openSUSE | T520 | C# | 经典的 | 不 | 111.9 毫秒 | 好 21% | |
5 | openSUSE | T520 | C++ | LINQ | 是的 | 250.9 毫秒 | 好 261 % | 不一致的 |
6 | openSUSE | T520 | C# | LINQ | 是的 | 906.2 毫秒 | 差 261% | |
7 | openSUSE | T520 | C++ | 经典的 | 是的 | 135.1 毫秒 | 3224 % 更好 | 不一致的 |
8 | openSUSE | T520 | C# | 经典的 | 是的 | 4491.5 毫秒 | 差 322 |