程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP基礎知識 >> Linq語言性能比較

Linq語言性能比較

編輯:PHP基礎知識
 

  我不只一次聽到不少做技術的朋友隨口一句,“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新特性的價值,而是希望能正確認識微軟創造出得如此神奇之作。到了今天這些其實已經算不上什麼新技術,甚至有些過時,但是,我奇怪的是不能接受一門新的技術還是不能沉下來看一看代碼!

很晚了,就此擱筆,有時間會繼續擴充,這個話題能寫的還有很多,當然,希望有這方面更好的文章出來大家一起分享。

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved