这两者有什么区别?
比较的最佳方式是什么?
plinq总是更好吗?
当我们使用plinq时?
答案 0 :(得分:10)
Linq是一组技术,它们协同工作来解决类似的问题 - 在所有这些技术中,您拥有数据源(xml文件或文件,数据库内容,内存中的对象集合)并且您想要检索部分或全部这些数据并以某种方式对其采取行动。 Linq致力于解决这一系列问题的共性:
var brithdays = from user in users where
user.dob.Date == DateTime.Today && user.ReceiveMails
select new{user.Firstname, user.Lastname, user.Email};
foreach(bdUser in birthdays)
SendBirthdayMail(bdUser.Firstname, bdUser.Lastname, bdUser.Email);
等效(使用传统的C#语法明确使用与Linq相关的类和方法):
var birthdays = users
.Where(user => user.dob.Date == DateTime.Today)
.Select(user => new{user.Firstname, user.Lastname, user.Email});
foreach(bdUser in birthdays)
SendBirthdayMail(bdUser.Firstname, bdUser.Lastname, bdUser.Email);
这两个代码示例都可以工作,无论是将其转换为数据库调用,解析xml文档还是通过对象数组进行搜索。
唯一的区别是对象users
是什么类型。如果它是一个列表,数组或其他可枚举的集合,它将是linq-to-objects,如果它是System.Data.Linq.Table
,它将是linq to sql。前者将导致内存操作,后者在SQL查询中,然后尽可能晚地反序列化为内存中的对象。
如果它是ParallelQuery
- 通过在内存中可枚举集合上调用.AsParallel
生成 - 那么查询将在mem-memroy,并行化(大多数时间)执行,以便执行通过多个线程 - 理想情况下保持每个核心忙于推进工作。
显然,这里的想法是更快。当它运作良好时,确实如此。
但是有一些缺点。
首先,即使在最终无法进行并行化的情况下,也需要一些开销来实现并行化。如果没有对数据进行足够的工作,这种开销将超过任何潜在的收益。
其次,并行处理的好处取决于可用的内核。如果查询不会最终阻塞4核计算机上的资源,理论上你可以获得4倍的加速(4个超线程可能会给你更多甚至更少,但可能不会超过8倍 - 线程加倍CPU的某些部分并没有给出明显的两倍增加。在单核上使用相同的查询,或者具有处理器亲和性意味着只有一个核可用(例如,“web-garden”模式中的web服务器),那么就没有加速。如果资源受阻,仍然可以获得收益,但好处取决于机器。
第三,如果有任何共享资源(可能正在输出集合结果)以非线程安全的方式使用,那么错误的结果,崩溃等可能会出现严重错误。
第四,如果以线程安全的方式使用共享资源,并且线程安全来自锁定,则可能有足够的争用成为瓶颈,从而解除并行化带来的所有好处。
第五,如果你的四核机器在四个不同的线程上运行或多或少相同的算法(可能是由于四个客户端在客户端 - 服务器情况下,或者在一组类似任务的桌面情况下)在这个过程中更高,然后他们总是充分利用这些核心。将算法中的工作拆分以便在所有四个核心中进行处理意味着您已经从四个线程(每个线程使用一个核心)移动到四个核心的16个线程。充其量只会是相同的,并且可能的开销会使它稍微恶化。
在很多情况下,可以仍然是一个重大胜利,但上述内容应该明确表明并非总是如此。
答案 1 :(得分:3)
鉴于AsParallel透明地并行化LINQ查询,问题出现了,“为什么Microsoft不能简单地并行化标准查询运算符并使PLINQ成为默认值?”
选择加入方法有很多原因。首先,要使PLINQ有用,必须有合理数量的计算密集型工作才能将其转移到工作线程。大多数LINQ to Objects查询执行速度非常快,不仅不需要并行化,而且分区,整理和协调额外线程的开销实际上可能会减慢速度。
此外:
关于元素排序,PLINQ查询的输出(默认情况下)可能与LINQ查询不同。
以下查询运算符会阻止查询并行化,除非源元素位于其原始索引位置:
径,TakeWhile,跳过和SkipWhile选择,的SelectMany和ElementAt的大多数查询运算符的索引版本改变元件(包括那些删除元素,诸如在哪里)的分度位置。这意味着如果你想使用前面的运算符,它们通常需要在查询的开头。
以下查询运算符是可并行化的,但使用昂贵的分区策略,有时可能比顺序处理慢:
加入,的GroupBy,群组加入,层次分明,联盟,交叉,除总运营商在他们的标准化身种子重载不是并行 - PLINQ提供特殊重载应对这种
何时使用PLINQ 在您的现有应用程序中搜索LINQ查询并尝试并行化它们是很诱人的。这通常是非生产性的,因为LINQ显然是最佳解决方案的大多数问题往往执行得非常快,因此不能从并行化中受益。更好的方法是找到CPU密集型瓶颈,然后考虑“这可以表示为LINQ查询吗?”(这种重组的一个受欢迎的副作用是LINQ通常使代码更小,更易读。)
PLINQ非常适合令人尴尬的并行问题。它也适用于结构化阻塞任务,例如一次调用多个Web服务(请参阅调用阻塞或I / O密集型函数)。
PLINQ可能是成像的不良选择,因为将数百万像素整理成输出序列会产生瓶颈。相反,最好将像素直接写入数组或非托管内存块,并使用Parallel类或任务并行来管理多线程。 (但是,有可能使用ForAll来击败结果排序。如果图像处理算法自然适用于LINQ,那么这样做是有意义的。)
答案 2 :(得分:2)
PLinq是Linq的并行版本。一些查询可以在多个线程上执行,然后PLinq可以提高性能。
但是,其他查询不能并行执行,或者如果这样做会产生错误的结果。因此,何时使用PLinq是您应该为每个查询决定的,并确保性能实际上增加。
MSDN上有很多文档。
答案 3 :(得分:1)
我还想知道何时使用PLINQ而不是LINQ,所以我进行了一些测试。
摘要: 在决定使用LINQ还是PLINQ运行查询时,有两个问题需要回答。
运行查询涉及多少次迭代(集合中有多少个对象)?
一个迭代涉及多少工作?
除非PLINQ性能更高,否则请使用LINQ。如果查询集合涉及太多的迭代并且/或者每个迭代涉及太多的工作,则PLINQ的性能可能比LINQ高。
但是随后出现了两个难题:
我的建议是测试您的查询。使用LINQ进行一次测试,然后使用PLINQ进行一次测试,然后比较两个结果。
测试1:通过增加集合中的对象数量来增加查询的迭代次数。
初始化PLINQ的开销大约需要20ms。如果没有利用PLINQ的优势,这将浪费时间,因为LINQ的开销为0毫秒。
每次测试涉及的迭代工作始终相同。工作量很小。
工作定义:将int(集合中的对象)乘以10。
当迭代每次迭代所需的工作量最少的100万个对象时,PLINQ比LINQ快。尽管在专业环境中,我从未查询或初始化过内存中的1000万个对象的集合,所以这在PLINQ恰好优于LINQ的情况下是不太可能的。
╔═══════════╦═══════════╦════════════╗
║ # Objects ║ LINQ (ms) ║ PLINQ (ms) ║
╠═══════════╬═══════════╬════════════╣
║ 1 ║ 1 ║ 20 ║
║ 10 ║ 0 ║ 18 ║
║ 100 ║ 0 ║ 20 ║
║ 1k ║ 0 ║ 23 ║
║ 10k ║ 1 ║ 17 ║
║ 100k ║ 4 ║ 37 ║
║ 1m ║ 36 ║ 76 ║
║ 10m ║ 392 ║ 285 ║
║ 100m ║ 3834 ║ 2596 ║
╚═══════════╩═══════════╩════════════╝
测试2:增加迭代中涉及的工作
我将集合中的对象数始终设置为10,因此查询涉及的迭代次数很少。对于每个测试,我都增加了处理每次迭代的工作。
工作定义:将int(集合中的对象)乘以10。
增加工作量的定义:增加将int乘以10的迭代次数。
当工作迭代中的迭代次数增加到1000万时,工作量显着增加,因此PLINQ查询集合的速度更快。我得出的结论是,当单次迭代涉及此工作量时,PLINQ优于LINQ。 / p>
此表中的“#迭代次数”表示工作迭代内的迭代次数。请参见下面的测试2代码。
╔══════════════╦═══════════╦════════════╗
║ # Iterations ║ LINQ (ms) ║ PLINQ (ms) ║
╠══════════════╬═══════════╬════════════╣
║ 1 ║ 1 ║ 22 ║
║ 10 ║ 1 ║ 32 ║
║ 100 ║ 0 ║ 25 ║
║ 1k ║ 1 ║ 18 ║
║ 10k ║ 0 ║ 21 ║
║ 100k ║ 3 ║ 30 ║
║ 1m ║ 27 ║ 52 ║
║ 10m ║ 263 ║ 107 ║
║ 100m ║ 2624 ║ 728 ║
║ 1b ║ 26300 ║ 6774 ║
╚══════════════╩═══════════╩════════════╝
测试1个代码:
class Program
{
private static IEnumerable<int> _numbers;
static void Main(string[] args)
{
const int numberOfObjectsInCollection = 1000000000;
_numbers = Enumerable.Range(0, numberOfObjectsInCollection);
var watch = new Stopwatch();
watch.Start();
var parallelTask = Task.Run(() => ParallelTask());
parallelTask.Wait();
watch.Stop();
Console.WriteLine($"Parallel: {watch.ElapsedMilliseconds}ms");
watch.Reset();
watch.Start();
var sequentialTask = Task.Run(() => SequentialTask());
sequentialTask.Wait();
watch.Stop();
Console.WriteLine($"Sequential: {watch.ElapsedMilliseconds}ms");
Console.ReadKey();
}
private static void ParallelTask()
{
_numbers
.AsParallel()
.Select(x => DoWork(x))
.ToArray();
}
private static void SequentialTask()
{
_numbers
.Select(x => DoWork(x))
.ToArray();
}
private static int DoWork(int @int)
{
return @int * 10;
}
}
测试2代码:
class Program
{
private static IEnumerable<int> _numbers;
static void Main(string[] args)
{
_numbers = Enumerable.Range(0, 10);
var watch = new Stopwatch();
watch.Start();
var parallelTask = Task.Run(() => ParallelTask());
parallelTask.Wait();
watch.Stop();
Console.WriteLine($"Parallel: {watch.ElapsedMilliseconds}ms");
watch.Reset();
watch.Start();
var sequentialTask = Task.Run(() => SequentialTask());
sequentialTask.Wait();
watch.Stop();
Console.WriteLine($"Sequential: {watch.ElapsedMilliseconds}ms");
Console.ReadKey();
}
private static void ParallelTask()
{
_numbers
.AsParallel()
.Select(x => DoWork(x))
.ToArray();
}
private static void SequentialTask()
{
_numbers
.Select(x => DoWork(x))
.ToArray();
}
private static int DoWork(int @int)
{
const int numberOfIterations = 1000000000;
for (int i = 0; i < numberOfIterations; i++)
{
@int = @int * 10;
}
return @int;
}
}
答案 4 :(得分:0)
在使用anonymous types时考虑避免使用PLINQ,因为根据Threading in C#, by Joe Albahari:
匿名类型(是类,因此是引用类型)会导致基于堆的分配和后续垃圾回收的成本。
(...)
基于堆栈的分配是高度可并行化的(因为每个线程都有自己的堆栈),而所有线程必须竞争同一个堆 - 由单个内存管理器和垃圾收集器管理。
答案 5 :(得分:0)
PLINQ通过更有效地使用主机上的所有可用内核,可以大大提高LINQ到对象的查询速度。更高的性能为台式机带来了高性能的计算能力。