C# PLINQ 内存列表查询优化历程

 更新时间:2016年03月24日 08:57:29   作者:qwsf01115  
这篇文章主要介绍了C# PLINQ 内存列表查询优化历程的相关资料,需要的朋友可以参考下

产品中(基于ASP.NET MVC开发)需要经常对药品名称及名称拼音码进行下拉匹配及结果查询。为了加快查询的速度,所以我最开始就将其加入内存中(大约有六万五千条数据)。

下面附实体类。

public class drugInfo
{
  public int drug_nameid  { get; set; }
  public string drug_name  { get; set; }
  public string drug_search_code  { get; set; }
}

第一次做法:

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
key = key.ToLower();
var resultList = cacheList.Where(m => m.drug_name.ToLower().Contains(key) || m.drug_search_code.ToLower().Contains(key)).ToList();
stopWatch.Stop();
double eMseconds = Math.Max(0, stopWatch.Elapsed.TotalSeconds);

刷新页面几次,得到个平均用时约35MS左右。

第二次做法:

为了减少CPU的运算,我们将LINQ表达式中的转小写操作优化一下,先在缓存列表上做些动作,将名称和搜索码先转小写存储。

下面为改进过的实体类。

public class drugInfo
{
  public int drug_nameid  { get; set; }
  public string drug_name  { get; set; }
  public string drug_search_code  { get; set; }
  public string lower_drug_name  { get; set; }
  public string lower_drug_search_code  { get; set; }
}
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
key = key.ToLower();
var resultList = cacheList.Where(m => m.lower_drug_name.Contains(key) || m.lower_drug_search_code.Contains(key)).ToList();
stopWatch.Stop();
double eMseconds = Math.Max(0, stopWatch.Elapsed.TotalSeconds);
ViewBag.useTime = string.Format("用时{0}秒\r\n", eMseconds);

刷新页面几次,得到个平均用时约16MS左右。

虽然这样做,内存列表中会多一些冗余数据,但是得到的性能提升有一倍了。

第三次做法:

启用PLINQ的并行计算,并行计算是NET4.0的特性,可以利用CPU多核的处理能力,提高运算效率,但是不一定是成倍的
LIST等泛型启用并行计算很简单,使用AsParallel()即可,改进如下:

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
key = key.ToLower();
var resultList = cacheList.AsParallel().Where(m => m.lower_drug_name.Contains(key) || m.lower_drug_search_code.Contains(key)).ToList();
stopWatch.Stop();
double eMseconds = Math.Max(0, stopWatch.Elapsed.TotalSeconds);
ViewBag.useTime = string.Format("用时{0}秒\r\n", eMseconds);

同样,我们多刷新页面几次,获得的平均时间为10MS左右。

当然,写到这里,大家以为这次的优化就结束了,至少我当时是这么想的。
---------------------------------------------------------------------------------------------------
但是事实上,碰到了一个大麻烦。

由于产品运行于服务器IIS上面,使用AsParallel并行特性时(默认情况下,到底使用多少个线程来执行PLINQ是在程序运行时由TPL决定的。但是,如果你需要限制执行PLINQ查询的线程数目(通常需要这么做的原因是有多个用户同时使用系统,为了服务器能同时服务尽可能多的用户,必须限制单个用户占用的系统资源),我们可以使用ParallelEnumerable. WithDegreeOfParallelism()扩展方法达到此目的。),客户端一个请求就占用了过多的系统资源,导致应用程序池假死。无法提供服务。

我也尝试过使用WithDegreeOfParallelism设置了一个相对较少的值,但是在使用LOADRUNNER来开启200个并发的时候,也会产生假死的情况,于是,不得不尝试下面第四步的办法。

第四次做法:

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
key = key.ToLower();
ConcurrentBag<drugInfo> resultList = new ConcurrentBag<drugInfo>();
Parallel.For(0, cacheList.Count, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (i) =>
{
var item = cacheList[i];
if (item.lower_drug_name.Contains(key) || item.lower_drug_search_code.Contains(key))
{
resultList.Add(item);
}
});
stopWatch.Stop();
double eMseconds = Math.Max(0, stopWatch.Elapsed.TotalSeconds);
ViewBag.useTime = string.Format("用时{0}秒\r\n", eMseconds);

时间与第三步没有什么区别,但是这样做解决了并发时,应用程序池假死的问题。至此,困扰两天的问题完美解决,虽然使用Parallel.For会带来结果乱序的问题,但是结果数量已经不多了,再次排序也没有什么关系了。

具体原因参见下面:

ParallelOptions.MaxDegreeOfParallelism指明一个并行循环最多可以使用多少个线程。TPL开始调度执行一个并行循环时,通常使用的是线程池中的线程,刚开始时,如果线程池中的线程很忙,那么,可以为并行循环提供数量少一些的线程(但此数目至少为1,否则并行任务无法执行,必须阻塞等待)。等到线程池中的线程完成了一些工作,则分配给此并行循环的线程数目就可以增加,从而提升整个任务完成的速度,但最多不会超过ParallelOptions.MaxDegreeOfParallelism所指定的数目。

PLINQ的WithDegreeOfParallelism()则不一样,它必须明确地指出需要使用多少个线程来完成工作。当PLINQ查询执行时,会马上分配指定数目的线程执行查询。

之所以PLINQ不允许动态改变线程的数目,是因为许多PLINQ查询是“级联”的,为保证得到正确的结果,必须同步参与的多个线程。如果线程数目不定,则要实现线程同步非常困难。

有关C# PLINQ 内存列表查询优化历程小编就给大家介绍这么多,希望对大家有所帮助!

相关文章

  • c#访问this关键字和base关键字示例

    c#访问this关键字和base关键字示例

    this关键字引用类的当前实例。静态成员方法中不能使用this关键字,this关键字只能在实例构造函数、实例方法或实例访问器中使用。base关键字用于从派生类中访问基类的成员。下面学习一下这二个关键字的使用方法
    2014-01-01
  • 深入理解C#表达式树的使用

    深入理解C#表达式树的使用

    表达式树是C#编程语言中一个强大的特性,本文将深入探讨表达式树的基本概念、创建方法、节点类型、遍历技巧以及在C#中的应用示例,感兴趣的可以了解一下
    2024-03-03
  • C#使用foreach语句遍历集合类型的方法

    C#使用foreach语句遍历集合类型的方法

    这篇文章主要介绍了C#使用foreach语句遍历集合类型的方法,可实现通过foreach语句遍历集合类的功能,需要的朋友可以参考下
    2015-06-06
  • C# NetRemoting实现双向通信

    C# NetRemoting实现双向通信

    本篇文章主要介绍了C# NetRemoting实现双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的
    2017-03-03
  • c# Thread类线程常用操作详解

    c# Thread类线程常用操作详解

    这篇文章主要介绍了c# Thread类线程常用操作详解的相关资料,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-03-03
  • C#中Ilist与list的区别小结

    C#中Ilist与list的区别小结

    本篇文章主要是对C#中Ilist与list的区别进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • C#实现Excel转PDF时设置内容适应页面宽度

    C#实现Excel转PDF时设置内容适应页面宽度

    将Excel转为PDF格式时,通常情况下转换出来的PDF页面都是默认的宽度大小。所以本文提供了C#实现Excel转PDF时设置内容适应页面宽度的示例代码,需要的可以参考一下
    2022-04-04
  • C#面向对象编程之猜拳游戏实现方法

    C#面向对象编程之猜拳游戏实现方法

    这篇文章主要介绍了C#面向对象编程之猜拳游戏实现方法,以一个完整的猜拳游戏为例讲述了C#面向对象程序设计的具体实现步骤,具有一定的学习与借鉴价值,需要的朋友可以参考下
    2014-11-11
  • C#生成影像金字塔的原理实例

    C#生成影像金字塔的原理实例

    最近在处理一个关于影像金字塔的问题,这个金字塔程序是用C#写的,需要的朋友可以参考一下
    2013-05-05
  • 关于C#执行顺序带来的一些潜在问题

    关于C#执行顺序带来的一些潜在问题

    这篇文章主要给大家介绍了关于C#执行顺序带来的一些潜在问题,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-08-08

最新评论