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# PLINQ 内存列表查询优化历程
http://www.cnblogs.com/dengxi/p/5305066.html 产品中(基于ASP.NET MVC开发)需要经常对药品名称及名称拼音码进行下拉匹配及结果查询.为了加快查询的速 ...
- Java内存列表
当jvm运行起来的时候,它会向系统申请一片内存区,并将这块内存分出一部分存储程序创建的对象,传递给方法的参数,返回值,局部变量等等,我们将这块内存称之为“运行时数据区”. 初学的时候把Java内存分为 ...
- C++ 海量代码 排查内存/GDI泄漏历程
排查分两大部分: 1.代码静态分析,通过Code Review查找不合规范的代码点: 2.运行目标软件,结合内存监控工具,分析目标软件的代码,定位内存泄漏点. 目前能找到的代码静态分析软件:Cover ...
- Java虚拟机内存管理原理基础入门
Jdk:Java程序设计语言.Java虚拟机.Java API类库. Jdk是用于支持Java程序开发的最小环境. Jre:Java API类库中的Java SE API子集.Java虚拟机. Jre ...
- C语言中内存分配那些事儿
C程序的内存结构 C语言的之所以复杂,首先它的内存模型功不可没.不像某些那样的高级语言只需要在使用对象的时候,用new创建.所有之后的事情,你不需要操心.对于C语言,所有与内存相关的东西,都需要熟悉, ...
- php内存处理须知【转】
04 Mar 11 深入理解PHP内存管理之谁动了我的内存 作者: Laruence( ) 本文地址: http://www.laruence.com/2011/03/04/1894.html 转 ...
- KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
- php内存管理
1.为什么需要内存管理 由于计算机的内存由操作系统进行管理,所以普通应用程序是无法直接对内存进行访问的, 应用程序只能向操作系统申请内存,通常的应用也是这么做的,在需要的时候通过类似malloc之类的 ...
- 普通内存、ECC内存和REG ECC内存有什么不同
都知道,在INTEL平台,北桥负责与CPU的联系,并控制内存.AGP.PCI数据在北桥内部传输.基本上只要主板芯片组确定,那么其支持的内存类型也就确定了. INTEL芯片组划分的很清楚,865PE属于 ...
随机推荐
- UVaLive 7361 Immortal Porpoises (矩阵快速幂)
题意:求Fibonacci的第 n 项. 析:矩阵快速幂,如果不懂请看http://www.cnblogs.com/dwtfukgv/articles/5595078.html 是不是很好懂呢. 代码 ...
- 数据持久化之sharedpreference的使用
要将数据持久化到手机移动设备有多种方法,其中有一种是通过sharedpreference来实现. 首先将sharedpreference初始, private SharedPreferences sp ...
- 设置mysql服务器远程连接
使用“Ctrl + R”组合键快速打开cmd窗口,并输入“cmd”命令,打开cmd窗口. 使用“mysql -uroot -proot”命令可以连接到本地的mysql服务. 使用“use mysql” ...
- [置顶] Quartz的DateBuilder详解
DateBuilder类有两个方法: nextGivenMinuteDate和nextGivenSecondDate: Method: (a) public static Date nextG ...
- Java中字符串相等与大小比较
国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html 内部邀请码:C8E245J (不写邀请码,没有现金送) 国 ...
- C语言高效编程的几招(绝对实用,绝对经典)
编写高效简洁的C语言代码,是许多软件工程师追求的目标.废话不说,走起! 第一招:以空间换时间 计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题 eg.字符串的 ...
- 使用TopShelf轻松开发Window服务
关于TopShelf 描述: Topshelf is a framework for hosting services written using the .NET framework. The cr ...
- BOOL,int,float,指针变量与零值比较的if语句
1.注意这里说的是,与零值比较,而不是与零比较. 2.对于int类型,与零值比较就是: if(var == 0) //零值 3.对于bool类型,零值表示false,任何非零值表示true,因此使用: ...
- Python 类型的分类
1.存储模型,对象可以保存多少个值.如果只能保存一个值,是原子类型.如果可以保存多个值,是容器类型.数值是原子类型,元组,列表,字典是容器类型.考虑字符串,按道理,字符串应该是容器类型,因为它包含多个 ...
- Ogre分层渲染 (转)
Ogre分层渲染 转载请注明出处!http://www.cnblogs.com/pulas 在超大的场景中,如果既想看到近处的物体,又想看到很远的物体,则必须把相机的远近裁剪面距离设得很大.远近裁剪面 ...