当前位置: 移动技术网 > IT编程>开发语言>c# > C# PLINQ 内存列表查询优化历程

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

2019年07月18日  | 移动技术网IT编程  | 我要评论

产品中(基于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 内存列表查询优化历程小编就给大家介绍这么多,希望对大家有所帮助!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网