我不只一次听到不少做技术的朋友随口一句,“linq性能是最差的”,由于缺少具体的数字比照也就没在意,但心里隐隐觉得事实应该不是这样的,我记得我第一次听到有人贬低C# 3.0是在我工作后不久的一个夏季,天气很热,吃完晚饭有个朋友给我电话说刚在项目中用了3.0的技术,非常差劲,非常慢,我当时就觉得纳闷,不能呀,微软不可能搞出一个性能大家公认的差产品。由于当时一直关注老赵MVC beta版本技术,没太在意。其后不久开始尝试使用了EF框架,感觉没有传说中的垃圾,由于这次的尝鲜,对于net新技术就一发不可收拾地运用了起来。那时entity framework类似的技术还有一个叫linq to sql,而后不久微软合并了这两个项目,变成了统一的EF框架。首先请大家原谅我的不专业用词linq语言,我这里讨论的就是C# 3.0之后引入的linq to sql , Entity Framewokr等技术。
根据自己在社区和工作中的亲身体验来看,linq的性能一直都在提高,在最开始的两个技术版本中映射数据库的性能估计是有待提高,但在其他方面个人觉得是提高了很大的效率,当然纯个人见解,可能有很多人能罗列出一千个一万个理由否定我的观点,但我认同2点,基于这2点在项目中,乃至大型项目中运用EF框架是没有问题的。
1、比起很多大项目做到最后自定义ORM技术框架,忙于改bug和忙于升级,不与直接使用EF,在此基础上做二次优化
2、在代码量上有很大的缩减。
当然,一家之言,好了,闲话到此,进入正题,有数据和测试用例来说明linq语言、Entity Framework技术的性能。
我的环境:
硬件:Thinkpad t430 I5(2.8GHz) /8G DDR3
软件支持:Visual Studio 2012 Ultimate + MSSQL 2012
支持操作系统: Windows 8 Enterprise
一、集合操作测试
打开vs2012,创建控制台应用程序,我取的工程名为ConsoleApplication6,在program.cs文件中创建2个实体类Doc和InFast,便于做集合操作测试。
public class Doc { public string DocId { get; set; } public string DocName { get; set; } } public class InFast { public string DOCID { get; set; } public string FILEPATH { get; set; } public string MIMETYPE { get; set; } public string ENTITYID { get; set; } public string DOCDATETIME { get; set; } public string EXPDATE { get; set; } public string SUBCONTENTFORMAT { get; set; } public string UPDATETIME { get; set; } public string DOCTYPE { get; set; } public string DOCCONTENTTYPE { get; set; } public string INEFFECTIVE { get; set; } public string STACKSTATUS { get; set; } public string DISPLAYDOCID { get; set; } public string Importance { get; set; } }
创建一个9000000个元素的DOC类对象的泛型集合,分布使用传统的for循环和linq语言进行集合操作,比教性能。为了代码的整洁性,我们将这些操作封装到一个类当中,代码如下:
public class TestOperation { public void OperationFor() { List<Doc> docList = new List<Doc>(); for (int i = 0; i < 9000000; i++) { Doc d = new Doc(); d.DocId = Guid.NewGuid().ToString(); d.DocName = i.ToString(); docList.Add(d); } System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); StringBuilder sres = new StringBuilder(); foreach (Doc d in docList) { sres.Append(d.DocName); } sw.Stop(); Console.WriteLine(string.Format("foreach take times {0}ms", sw.ElapsedMilliseconds)); sw.Restart(); StringBuilder sfor = new StringBuilder(); for (int i = 0; i < docList.Count; i++) { sfor.Append(docList[i].DocName); } sw.Stop(); Console.WriteLine(string.Format("for take times {0}ms", sw.ElapsedMilliseconds)); sw.Restart(); StringBuilder smy = new StringBuilder(); docList.ForEach(p => { smy.Append(p.DocName); }); sw.Stop(); Console.WriteLine(string.Format("Linq Foreach take times {0}ms", sw.ElapsedMilliseconds)); }
在这里首先使用for循环创建了9000000个元素的List<Doc>集合,然后分别用Foreach和for循环操作集合,循环中都做了同样的事情,取出每个元素的一个对象属性的值,拼接到StringBuilder上去然后输入,在这里我们最关心的是,这两种集合操作方式各消耗的时间如何。
运行结果如下:
如此重复,我们连续测试五次查看测试结果
类别 | 第一次(ms) | 第二次(ms) | 第三次(ms) | 第四次(ms) | 第五次(ms) |
Foreach | 352 | 338 | 311 | 375 | 356 |
For | 329 | 318 | 297 | 315 | 305 |
Linq Foreach | 309 | 294 | 270 | 284 | 291 |
测试结果不言而喻,在时间消耗上linq foreach是最少的,当然如果在占用cpu和内存消耗角度而言,就需要借助第三方工具了。
二、数据库操作测试
数据库连接查询操作测试毫无疑问,首先需要有一个数据和相应的表进行测试,为了便于进行查询和插入两个操作,我们先进行插入操作比较,然后使用插入的数据进行查询操作测试。
进入MSSQL床架数据库APlatformJolAppUser,并创建表lnFastDocument,脚本如下:
create database APlatformJolAppUser go use APlatformJolAppUser go create table lnFastDocument ( DOCID varchar(50) primary key not null, FILEPATH varchar(50), MIMETYPE varchar(50), ENTITYID varchar(50), DOCDATETIME datetime, EXPDATE varchar(50), SUBCONTENTFORMAT varchar(50), UPDATETIME varchar(50), DOCTYPE varchar(50), DOCCONTENTTYPE varchar(50), INEFFECTIVE varchar(50), STACKSTATUS varchar(50), DISPLAYDOCID varchar(50), Importance varchar(50) ) go
完成数据库和表的创建之后,进入vs,打开program.cs文件,将数据库测试操作封装一个方法OperationInsertData和OperationDatabase,将数据库的操作单独封装为一个类文件DataBase_Util.cs,这里需要使用EF,所以在完成数据库创建之后,引入Entity Framework,这里使用EF框架的Database First模式。
DataBase_Util.cs类文件主要封装了进行传统操作的SqlCommand、SqlCommand、SqlDataAdapter等对象对数据库的基本操作。代码如下:
public class DataBase_Util { private static string Connection_String = "Initial Catalog=APlatformJOL;User ID=sa;Password=sasasa;Data Source=."; private SqlConnection _connection = null; private SqlCommand _command = null; internal SqlConnection GetConnection { get { if (_connection == null) { _connection = new SqlConnection(Connection_String); } return _connection; } } internal SqlCommand GetCommand { get { if (_command == null) { _command = GetConnection.CreateCommand(); } return _command; } } internal DataTable ExecSQL(string sql, params SqlParameter[] pars) { GetCommand.Parameters.Clear(); GetCommand.CommandText = sql; GetCommand.CommandType = CommandType.Text; GetCommand.Parameters.AddRange(pars); if (GetConnection.State != ConnectionState.Open) { GetConnection.Open(); } DataSet ds = new DataSet(); try { SqlDataAdapter sda = new SqlDataAdapter(GetCommand); sda.Fill(ds); } catch (Exception ex) { GetConnection.Close(); throw ex; //return false; } GetConnection.Close(); return ds.Tables[0]; } internal void ExecSQLNoRtn(string sql) { GetCommand.Parameters.Clear(); GetCommand.CommandText = sql; GetCommand.CommandType = CommandType.Text; if (GetConnection.State != ConnectionState.Open) { GetConnection.Open(); } try { GetCommand.ExecuteNonQuery(); } catch (Exception ex) { GetConnection.Close(); throw ex; //return false; } GetConnection.Close(); } internal bool ExecProcedure(string procName, params SqlParameter[] pars) { GetCommand.Parameters.Clear(); GetCommand.CommandText = procName; GetCommand.CommandType = CommandType.StoredProcedure; GetCommand.Parameters.AddRange(pars); if (GetConnection.State != ConnectionState.Open) { GetConnection.Open(); } try { GetCommand.ExecuteNonQuery(); } catch (Exception ex) { GetConnection.Close(); throw ex; //return false; } GetConnection.Close(); return true; } internal DataTable ExecProcedureDataTable(string procName, params SqlParameter[] pars) { GetCommand.Parameters.Clear(); GetCommand.CommandText = procName; GetCommand.CommandType = CommandType.StoredProcedure; if (pars != null) { GetCommand.Parameters.AddRange(pars); } SqlDataAdapter adapter = new SqlDataAdapter(GetCommand); DataSet ds = new DataSet(); if (GetConnection.State != ConnectionState.Open) { GetConnection.Open(); } try { adapter.Fill(ds); } catch (Exception ex) { GetConnection.Close(); throw ex; //return false; } GetConnection.Close(); return ds.Tables[0]; } }
首先创建了静态私有字段Connection_String,用于保存数据库连接字符串,Connection和Command用户数据库的操作,每次在类的实际操作函数中进行实例化和赋值。
internal DataTable ExecSQL(string sql, params SqlParameter[] pars) //带参数的sql语句执行函数,执行的sql返回datatable,取值操作
internal void ExecSQLNoRtn(string sql) //不带参数执行一段sql,典型的insert语句调用函数
internal bool ExecProcedure(string procName, params SqlParameter[] pars) //存储过程调用函数,这里暂时不会用到
再次,创建Entity Framework数据实体模型
准备工作完成之后,首先在原先的TestOperation类中再添加一个方法OperationInsertData,在这个方法中进行两个操作,第一个操作是运用传统的Sql向数据插入100000条数据,再使用EF框架插入100000条数据,比较性能。
运行结果:
连续进行5次测试:
类型 | 第一次(ms) | 第二次(ms) | 第三次(ms) | 第四次(ms) | 第五次(ms) |
SQL | 3097 | 3538 | 3371 | 3301 | 3619 |
Entity Framework | 5568 | 5440 | 5432 | 5244 | 5313 |
由此显而易见在数据的执行操作上Entity Framework的确慢了近一个级别,这不是EF的长处,但是代码量确少了不是一个级别,所以这个具体实践中看具体的项目需求,这里不做过多的论断,以免遭砖拍。
好,看完了插入操作我们再来看看关心的查询操作。同样在TestOperation类中封装查询操作的方法,方法中用传统sql和EF进行数据查询,代码如下:
public void OperationDatabase() { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); DataBase_Util dUtil = new DataBase_Util(); DataTable dtblRes = dUtil.ExecSQL("select top 90000 * from lnFastDocument"); List<InFast> fastList = new List<InFast>(); for (int i = 0; i < dtblRes.Rows.Count; i++) { InFast fst = new InFast(); fst.DOCID = dtblRes.Rows[i]["DOCID"].ToString(); fst.FILEPATH = dtblRes.Rows[i]["FILEPATH"].ToString(); fst.MIMETYPE = dtblRes.Rows[i]["MIMETYPE"].ToString(); fst.ENTITYID = dtblRes.Rows[i]["ENTITYID"].ToString(); fst.DOCDATETIME = dtblRes.Rows[i]["DOCDATETIME"].ToString(); fst.EXPDATE = dtblRes.Rows[i]["EXPDATE"].ToString(); fastList.Add(fst); } sw.Stop(); Console.WriteLine(string.Format("Query the database take times {0}ms", sw.ElapsedMilliseconds)); sw.Restart(); APlatformJolAppUserEntities dbContext = new APlatformJolAppUserEntities(); var fasts = dbContext.lnFastDocuments.Take(90000); List<InFast> fList = from f in fasts select new InFast { DOCID = f.DOCID, FILEPATH =f.FILEPATH, MIMETYPE =f.MIMETYPE, ENTITYID =f.ENTITYID, DOCDATETIME =f.DOCDATETIME.ToString(), EXPDATE =f.EXPDATE }; sw.Stop(); Console.WriteLine(string.Format("Linq the database take times {0}ms", sw.ElapsedMilliseconds)); }
两个操作都取90000条数据,运行结果如下:
同理,测试运行5次比较性能
类型 | 第一次(ms) | 第二次(ms) | 第三次(ms) | 第四次(ms) | 第五次(ms) |
SQL | 745 | 724 | 716 | 715 | 692 |
Entity Framework | 778 | 504 | 454 | 462 | 454 |
看到这里,我想一言断之linq语言性能太差的论断确实有点操之过急啦,当然,今天我写这篇文章,可能会遭到很多人的吐槽,尤其是大数据,新兴的服务架构模式,总之,我想说的是,我并不是抬高linq语言,这里还是专业点,并不是抬高linq to sql ,Entity Framework框架,这些net新特性的价值,而是希望能正确认识微软创造出得如此神奇之作。到了今天这些其实已经算不上什么新技术,甚至有些过时,但是,我奇怪的是不能接受一门新的技术还是不能沉下来看一看代码!
很晚了,就此搁笔,有时间会继续扩充,这个话题能写的还有很多,当然,希望有这方面更好的文章出来大家一起分享。