随着软件应用中计算密集型任务和大型数据集的日益普遍,开发者需要高效的工具来处理数据。在 C# 中,两个常用的数据处理工具是 LINQ(语言集成查询)和 PLINQ(并行 LINQ)。它们在语法和功能上相似,但在查询的执行方式上却有本质区别。
本文将深入探讨 LINQ 与 PLINQ 的主要差异、适用场景以及性能对比,同时通过实际示例和基准测试来说明它们的使用效果。
LINQ(Language Integrated Query,语言集成查询)是 C# 的一项功能,它允许开发者使用语言内嵌的查询语法来操作数据。自 .NET Framework 3.5 起引入,LINQ 提供了一种一致的方式来操作不同类型的数据源,比如集合、数据库、XML 等。
LINQ 查询是顺序执行的,即依次处理每个数据项。
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
foreach (var number in evenNumbers)
{
Console.WriteLine(number); // 输出: 2, 4
}
LINQ 使用简单,非常适合处理小型到中型数据集或计算量不大的查询。
PLINQ(Parallel LINQ,并行 LINQ)是在 .NET Framework 4.0 中引入的,它在 LINQ 的基础上增加了并行查询执行的能力。PLINQ 构建于 TPL(任务并行库)之上,利用多个 CPU 核心来更高效地处理大型数据集或计算密集型操作。
它会将数据划分为若干部分,并使用多个线程并发执行查询操作。
var numbers = Enumerable.Range(1, 10_000);
var evenNumbers = numbers.AsParallel()
.Where(n => n % 2 == 0)
.ToList();
Console.WriteLine(evenNumbers.Count); // 输出: 5000
调用 AsParallel()
方法后,查询将会在多个处理器核心上并行执行。
为了更好地理解 LINQ 与 PLINQ 在性能上的区别,我们来处理一个大型数据集,并对比它们的执行时间。
以下代码使用 LINQ 和 PLINQ 分别处理从 1 到 5,000,000 的整数集合,并筛选其中的素数,同时记录运行时间。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
classProgram
{
static void Main()
{
var largeDataSet = Enumerable.Range(1, 5_000_000).ToList();
// LINQ 测试
var stopwatch = Stopwatch.StartNew();
var linqPrimes = largeDataSet.Where(IsPrime).ToList();
stopwatch.Stop();
Console.WriteLine($"LINQ 时间: {stopwatch.ElapsedMilliseconds} 毫秒");
Console.WriteLine($"LINQ 素数数量: {linqPrimes.Count}");
// PLINQ 测试
stopwatch.Restart();
var plinqPrimes = largeDataSet.AsParallel().Where(IsPrime).ToList();
stopwatch.Stop();
Console.WriteLine($"PLINQ 时间: {stopwatch.ElapsedMilliseconds} 毫秒");
Console.WriteLine($"PLINQ 素数数量: {plinqPrimes.Count}");
}
static bool IsPrime(int number)
{
if (number <= 1) returnfalse;
for (int i = 2; i <= Math.Sqrt(number); i++)
{
if (number % i == 0) returnfalse;
}
returntrue;
}
}
默认情况下,PLINQ 是无序处理的,以最大化性能。但在某些情况下,如果你希望结果保持与原始数据相同的顺序,可以通过 .AsOrdered()
来显式指定有序执行。
.AsOrdered()
var numbers = Enumerable.Range(1, 10);
var orderedResult = numbers.AsParallel()
.AsOrdered()
.Where(n => n % 2 == 0)
.ToList();
Console.WriteLine(string.Join(", ", orderedResult)); // 输出: 2, 4, 6, 8, 10
如果结果顺序无关紧要,可以使用 .AsUnordered()
进一步优化性能。
以下代码比较有序与无序执行在 PLINQ 中的性能差异:
var numbers = Enumerable.Range(1, 1_000_000).ToList();
var stopwatch = Stopwatch.StartNew();
// 有序 PLINQ
var orderedPrimes = numbers.AsParallel()
.AsOrdered()
.Where(IsPrime)
.ToList();
stopwatch.Stop();
Console.WriteLine($"AsOrdered 执行时间: {stopwatch.ElapsedMilliseconds} 毫秒");
stopwatch.Restart();
// 无序 PLINQ
var unorderedPrimes = numbers.AsParallel()
.AsUnordered()
.Where(IsPrime)
.ToList();
stopwatch.Stop();
Console.WriteLine($"AsUnordered 执行时间: {stopwatch.ElapsedMilliseconds} 毫秒");
AsOrdered 执行时间: 210 毫秒
AsUnordered 执行时间: 140 毫秒
★可以看到,无序执行通常会更快,因为它省去了保持顺序所需的额外开销。
特性 | LINQ | PLINQ |
---|---|---|
执行方式 | 顺序执行 | 并行执行 |
性能优势 | 适合小型数据集 | 针对大型数据集优化 |
CPU 利用率 | 使用单个核心 | 使用多个核心和线程 |
顺序保持 | 默认保持输入顺序 | 默认无序,可通过 AsOrdered() 强制有序 |
错误处理 | 错误传播较简单 | 需要处理线程级别的异常 |
控制能力 | 控制执行方式有限 | 支持取消、分区等高级控制选项 |
系统开销 | 几乎无额外开销 | 线程管理和分区可能带来一定开销 |
LINQ 和 PLINQ 都是 C# 中非常强大且灵活的数据查询工具。
在使用 PLINQ 时,是否保持结果顺序会显著影响性能。如果对顺序没有硬性要求,使用 .AsUnordered()
能进一步提升处理速度。
总之,在实际项目中选择 LINQ 还是 PLINQ,应该根据以下几个因素权衡:
通过合理使用 LINQ 与 PLINQ,可以在保证代码简洁的同时最大化程序性能。
案例链接:
文件:PLINQ_LINQ_Comparison.zip
链接: https://pan.baidu.com/s/1vXXSJzmnaE13lFPh_r7NNw?pwd=8wfg 提取码: 8wfg
本文使用chatgpt协助翻译。
作者
:Ayush Gupta,版权归原作者Ayush Gupta所有
原文链接
:c-sharpcorner.com/article/plinq-vs-linq-use-cases-and-performance-insights-in-c-sharp/