Linq語言性能比較

 

  我不僅一次聽到很多作技術的朋友隨口一句,「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等技術。sql

     根據本身在社區和工做中的親身體驗來看,linq的性能一直都在提升,在最開始的兩個技術版本中映射數據庫的性能估計是有待提升,但在其餘方面我的以爲是提升了很大的效率,固然純我的看法,可能有不少人能羅列出一千個一萬個理由否認個人觀點,但我認同2點,基於這2點在項目中,乃至大型項目中運用EF框架是沒有問題的。數據庫

    一、比起不少大項目作到最後自定義ORM技術框架,忙於改bug和忙於升級,不與直接使用EF,在此基礎上作二次優化架構

    二、在代碼量上有很大的縮減。框架

固然,一家之言,好了,閒話到此,進入正題,有數據和測試用例來講明linq語言、Entity Framework技術的性能。函數

個人環境:工具

          硬件:Thinkpad t430 I5(2.8GHz) /8G DDR3性能

          軟件支持:Visual Studio 2012 Ultimate + MSSQL 2012 測試

          支持操做系統: Windows 8 Enterprise大數據

 

1、集合操做測試優化

 

  打開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和內存消耗角度而言,就須要藉助第三方工具了。

 

2、數據庫操做測試


  數據庫鏈接查詢操做測試毫無疑問,首先須要有一個數據和相應的表進行測試,爲了便於進行查詢和插入兩個操做,咱們先進行插入操做比較,而後使用插入的數據進行查詢操做測試。

  進入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新特性的價值,而是但願能正確認識微軟創造出得如此神奇之做。到了今天這些其實已經算不上什麼新技術,甚至有些過期,可是,我奇怪的是不能接受一門新的技術仍是不能沉下來看一看代碼!

很晚了,就此擱筆,有時間會繼續擴充,這個話題能寫的還有不少,固然,但願有這方面更好的文章出來你們一塊兒分享。

相關文章
相關標籤/搜索